上一篇中已经实现了通过IOC容器创建BEAN并管理, 在实际开发中BEAN之间的依赖是不可避免的. 例: 用户模块依赖于通用模块, 订单模块同时依赖于用户模块和通用模块等等. Spring提供了依赖注入, 自动的完成BEAN之间依赖的注入操作. 本篇中将通过代码实现依赖注入功能. ## 设计思路 通过代码定义了BEAN间的依赖关系时, Spring并不知道哪些属性需要其自动注入依赖实例, 因此需要通过配置告知Spring. 在声明BEAN的时候添加配置即可. ### XML配置 ``` <bean id="managerOne" class="com.atd681.xc.ssm.ioc.demo.ManagerOne"> <property name="maxSize" value="1024" /> <property name="serviceOne" ref="serviceOne" /> </bean> ``` `<property>`为BEAN的属性, name为属性名称, 属性值有两种方式 * 固定值: 属性值基础数据类型, 例: ``` int size = 10 String status = "ok" ``` * 其他BEAN: 属性值为依赖BEAN的实例. 例: ``` UserService service ``` 因此属性值需要增加类型来区分上述两种方式或者使用不同的XML属性. Spring采用后者. * 固定值: 使用属性value, 例: `<property name="maxSize" value="1024" />` * 其他BEAN: 使用属性ref, 例: `<property name="serviceOne" ref="serviceOne" />` ### 注解配置 BEAN中需要被注入的属性需要添加@AutoWired注解 ``` @Component public class ServiceTwo { // 添加@AutoWired注解告知Spring该属性需要自动注入 // 只有ServiceOne也通过IOC容器管理时才能注入 @AutoWired private ServiceOne serviceOne; // 未添加@AutoWired注解, Spring不会注入 private ServiceX serviceX; } ``` 确定好BEAN中需要被注入的属性后 , 在解析BEAN时将属性保存, 创建BEAN后从IOC容器中获取依赖的BEAN, 通过 JAVA 反射赋值至属性中即可. ## 代码实现 ### 增加BEAN属性描述类 用来保存BEAN中属性的基本信息, 包括属性(Field), 属性值, 类型(直接赋值,引用对象)等. ``` // BEAN属性描述 public class BeanProperty { // 属性类型: 直接赋值 public static final int TYPE_VAL = 1; // 属性类型: 引用其他对象 public static final int TYPE_REF = 2; // 属性 private Field field; // 属性值 private String value; // 属性类型 // 1: value为固定值, 例: int maxSize = 1024 // 2: value对应的BEAN的实例对象, 例: UserService service private int type; // 无参实例化 public BeanProperty() { } // 根据属性字段实例化(默认值为字段名称的实例对象) public BeanProperty(Field field) { this.field = field; this.value = BeanUtil.getName(field); this.type = TYPE_REF; } // Getter & Setter // ... } ``` ### BEAN描述类中增加属性集合 ``` // BEAN描述信息 public class BeanDefinition { // 名称, CLASS... // 需要被注入的属性集合 private List<BeanProperty> propertyList = new ArrayList<BeanProperty>(); // 添加属性 public void addProperty(BeanProperty property) { this.propertyList.add(property); } // Getter & Setter // ... } ``` ### 节点解析器中增加解析属性 * BeanElementParser 在解析BEAN节点时增加属性节点解析, 封装属性信息添加至BEAN描述的属性集合中. ``` // Bean节点解析器,解析XML配置文件中的<bean>节点 public class BeanElementParser implements ElementParser { // 解析<bean>节点 @SuppressWarnings("unchecked") @Override public void parse(Element ele, BeanFactory factory) throws Exception { // ... // <bean>节点中的id和class属性 // 封装成类描述信息 BeanDefinition bd = new BeanDefinition(id, clazz); // 解析属性 // 获取BEAN下所有属性节点 List<Element> propEleList = ele.getChildren("property"); for (Element propEle : propEleList) { // 根据属性名称BEAN中对应的属性 String propName = propEle.getAttributeValue("name"); Field field = clazz.getDeclaredField(propName); // 封装成属性描述信息 BeanProperty property = new BeanProperty(field); // 获取属性值(固定值) String propValue = propEle.getAttributeValue("value"); if (propValue != null) { property.setValue(propValue); property.setType(BeanProperty.TYPE_VAL); } // 获取属性引用BEAN的名称 // 同时定义value和ref时, ref属性将覆盖value属性 String propRef = propEle.getAttributeValue("ref"); if (propRef != null) { property.setValue(propRef); property.setType(BeanProperty.TYPE_REF); } // BEAN描述信息中添加属性 bd.addProperty(property); } // 向BEAN工厂注册Bean // ... } } ``` * ComponentScanElementParser 在解析BEAN时增加属性解析, 查找含有@AutoWired注解的属性添加至BEAN描述的属性集合中. ``` // <component-scan>节点解析器 public class ComponentScanElementParser implements ElementParser { // 解析<component-scan>节点 @Override public void parse(Element ele, BeanFactory factory) throws Exception { // ... // 获取扫描目录绝对路径 String baseDir = getClass().getClassLoader().getResource(basePackage.replace('.', '/')).getPath(); // 扫描目录,获取目录下的所有类文件 for (File file : new File(baseDir).listFiles()) { // ... // 封装成类描述信息 BeanDefinition bd = new BeanDefinition(c.value(), clazz); // 设置需要被注入的属性 Field[] fields = clazz.getDeclaredFields(); for (Field f : fields) { // 含有@AutoWired为需要被注入的属性 if (f.isAnnotationPresent(AutoWired.class)) { bd.addProperty(new BeanProperty(f)); } } // 向BEAN工厂注册Bean // ... } } } ``` ### 创建BEAN时增加依赖注入 ``` // BEAN工厂, 提供BEAN的创建及获取 public class BeanFactory { // 根据名称获取BEAN的实例 @SuppressWarnings("unchecked") public <T> T getBean(String name) throws Exception { // ... // 存在BEAN描述时根据描述信息实例化BEAN BeanDefinition beanDef = this.beanDefinitionMap.get(name); bean = beanDef.getClazz().newInstance(); // 对BEAN的属性进行诸如(依赖注入) populateBean(beanDef, bean); // 将BEAN实例化保存至容器 // ... } } ``` 依赖注入时根据属性类型获取对应的值, 通过反射将属性值设置到属性中. * 固定值: 直接获取定义的属性值 * BEAN引用: 从BEAN工厂获取依赖BEAN ``` // 依赖注入 public void populateBean(BeanDefinition bd, Object bean) throws Exception { // 获取BEAN中需要被注入的属性集合 List<BeanProperty> propertyList = bd.getPropertyList(); // 遍历属性, 根据属性信息注入 for (BeanProperty property : propertyList) { Object value; Field field = property.getField(); // 属性类型为固定值 if (property.getType() == BeanProperty.TYPE_VAL) { // 获取属性的值并转化为属性对应的类型 String fieldValue = property.getValue(); Class<?> fieldType = property.getField().getType(); value = BeanUtil.getValue(fieldValue, fieldType); } // 属性类型为BEAN引用 else { // 属性值(引用BEAN的名称) String refName = property.getValue(); // 根据名称从BEAN工厂中获取实例对象 value = getBean(refName); } // 通过反射对属性赋值, 完成依赖注入 field.setAccessible(true); field.set(bean, value); } } ``` ## 测试 * 创建BEAN ``` package com.atd681.xc.ssm.ioc.demo.service; import com.atd681.xc.ssm.ioc.framework.annotation.Component; @Component public class ServiceOne { public void list() { System.out.println("ServiceOne.list start..."); } } ``` ``` package com.atd681.xc.ssm.ioc.demo; import com.atd681.xc.ssm.ioc.demo.service.ServiceOne; public class ManagerOne { private ServiceOne serviceOne; private int maxSize; public void test() { System.out.println("ManagerOne.test start..."); System.out.println("ManagerOne.maxSize = " + this.maxSize); serviceOne.list(); System.out.println("ManagerOne.test end..."); } } ``` * 创建XML配置文件 ``` <?xml version="1.0" encoding="UTF-8"?> <beans> <!-- 配置BEAN所在目录, IOC容器会扫描该目录, 加载含有@Component注解的Bean --> <component-scan package="com.atd681.xc.ssm.ioc.demo.service" /> <!-- 配置BEAN及属性 --> <bean id="managerOne" class="com.atd681.xc.ssm.ioc.demo.ManagerOne"> <!-- 属性为固定值, 使用value --> <property name="maxSize" value="1024" /> <!-- 属性为BEAN引用, 使用ref --> <property name="serviceOne" ref="serviceOne" /> </bean> </beans> ``` * 创建测试类 ``` // IOC测试类 public class Test { // 测试IOC容器 public static void main(String[] args) throws Exception { // 实例化应用上下文并设置配置文件路径 ApplicationContext context = new ApplicationContext("context.xml"); // 初始化上下文(IOC容器) context.init(); ManagerOne managerOne = context.getBean("managerOne"); managerOne.test(); } } ``` * 运行 从IOC容器中获取属性对应的BEAN引用并赋值到属性中. ``` ManagerOne.test start... ManagerOne.maxSize = 1024 ServiceOne.list start... ManagerOne.test end... ``` 依赖注入时如果从IOC容器未找到对应的BEAN(未配置或配置错误)时会抛出异常: 获取BEAN引用出现错误. ``` Exception in thread "main" java.lang.RuntimeException: 未定义BEAN[serviceOne1] at com.atd681.xc.ssm.ioc.framework.BeanFactory.getBean(BeanFactory.java:59) at com.atd681.xc.ssm.ioc.framework.BeanFactory.populateBean(BeanFactory.java:121) at com.atd681.xc.ssm.ioc.framework.BeanFactory.getBean(BeanFactory.java:73) at com.atd681.xc.ssm.ioc.framework.BeanFactory.instanceBean(BeanFactory.java:89) at com.atd681.xc.ssm.ioc.framework.ApplicationContext.init(ApplicationContext.java:63) at com.atd681.xc.ssm.ioc.framework.ApplicationContext.init(ApplicationContext.java:49) at com.atd681.xc.ssm.ioc.demo.Test.main(Test.java:15) ``` 复制代码
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Head First PHP & MySQL(中文版)
Lynn Beighley、Michael Morrison / 苏金国、徐阳 / 中国电力 / 2010-6 / 98.00元
通过《深入浅出PHP&MySQL(影印版)》,你将学习:准备好把你的静态HTML网页提升到下一个层次并使用PHP和MySQL建立数据库驱动的网站了吗?《深入浅出PHP& MysQL》是一本快捷实用的指南,让你的动态网站快速运行。自己动手建立实际应用程序,从视频游戏高分留言板到在线交友网站。当你完成后,你将可以进行验证表单、使用会话ID和cookies工作、执行数据库查询和联接、处理文件I/0操作等......一起来看看 《Head First PHP & MySQL(中文版)》 这本书的介绍吧!