自定义异常
/** * 自定义异常 */ public class SpringException extends RuntimeException { public SpringException(String msg) { super (msg); } }
自定义注解
// 要设置注解的生命周期是运行时,如果不设置运行时,那么代码在运行时注解就被去掉了, spring 也就扫描不到了 @Retention (RetentionPolicy.RUNTIME) public @interface Service { public String value(); }
一、使用 XML 向容器中装配 Bean
public class BeanFactory { // 这里我们简单使用 map 来存储在 factory 中管理的 bean ,在真实的 spring 源码中是通过一个定义的数据结构来存储的,因为一个 bean 有很多数据需要存储,只靠一个 map 是远远不够的 Map<String, Object> map = new HashMap<String, Object>(); public BeanFactory(String xml) { parseXml(xml); } /** * 解析 XML 配置文件 * * @param xml */ public void parseXml(String xml) throws SpringException { // 生成 xml 文件对象 // 获取 xml 文件路径 String path = this .getClass().getResource( "/" ).getPath() + "//" + xml; try { // 解决路径中文乱码问题 path = URLDecoder.decode(path, "UTF-8" ); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } File file = new File(path); SAXReader reader = new SAXReader(); try { // 生成 xml 的 DOM 树 Document document = reader.read(file); // 获得 DOM 树上的根节点 Element elementRoot = document.getRootElement(); // 获取根标签中的 default-autowire 属性 Attribute attribute = elementRoot.attribute( "default-autowire" ); // 标记是否开启了自动装配 boolean flag = false; // 判断是否有 default-autowire ,即是否开启自动装配 if (attribute != null) { flag = true; } // 从根节点开始向下遍历树上的所有一级节点 for (Iterator<Element> it = elementRoot.elementIterator(); it.hasNext(); ) { /** * 1 、实例化对象 */ // 获取一个标签 Element elementFirstChil = it.next(); // 获取这个标签的 id 属性对象 Attribute attributeId = elementFirstChil.attribute( "id" ); // 获取 id 属性的值 String beanName = attributeId.getValue(); // 获取这个标签的 class 属性对象 Attribute attributeClass = elementFirstChil.attribute( "class" ); // 获取 class 属性的值 String clazzName = attributeClass.getValue(); // 获取 bean 的 class 对象 Class clazz = Class.forName(clazzName); // 生成对应 bean 的对象引用 Object object = null; /** * 2 、维护依赖关系 * 看这个对象有没有依赖(判断 xml 的 bean 标签中是否有 property 这个子标签 / 或者判断类是否有属性) * 如果有则注入 */ for (Iterator<Element> itSecond = elementFirstChil.elementIterator(); itSecond.hasNext(); ) { /** * 得到 ref 的 value, 通过 value 得到要注入的对象(从 map 中取得) * 得到 name 的值,根据这个值回去一个 Filed 对象( Filed 对象用来取得一个类中的属性) 这里为了简单默认使用 set 方法来进行注入,所以要取 name 值 * 通过 filed 的 set 方法来注入对象 */ // 取得二级子标签很替 Element elementSecondChil = itSecond.next(); // 查看 <bean> 标签的子标签中是否有 property 标签,有的话说明这个 bean 有依赖对象,需要用 setter 方法注入 if (elementSecondChil != null && "property" .equals(elementSecondChil.getName())) { // 生成对应 bean 的对象实例 object = clazz.newInstance(); // 获取依赖对象的 id String refValue = elementSecondChil.attribute( "ref" ).getValue(); // 从 map 中获取要注入的 bean Object injectObject = map.get(refValue); // 获取 <property> 标签的 name 属性,表示的是要匹配的 set 方法名,但是这里为了简单起见,就默认 service 对象 dao 的 set 方法名与 service 对象中 UserDao 属性名一致 , 都是 dao String nameValue = elementSecondChil.attribute( "name" ).getValue(); // 获取该类属性名为 nameValue 的属性的 Field 对象,来进行注入 Field field = clazz.getDeclaredField(nameValue); // 因为这个属性是私有的,所以要开启暴力反射,将 Accessible 设置为 true field.setAccessible(true); // 将要注入的对象注入到 object 中,这里 object 表示的是被注入的对象,也就是 service , injectObject 表示的是注入的对象,也就是 dao field.set(object, injectObject); // 使用构造方法注入 } else if (elementSecondChil != null && "constructor-arg" .equals(elementSecondChil.getName())){ // 获取依赖对象的 id String refValue = elementSecondChil.attribute( "ref" ).getValue(); // 从 map 中获取要注入的 bean Object injectObject = map.get(refValue); // 获取被注入对象的属性名 Attribute nameAttribute = elementSecondChil.attribute( "name" ); String nameValue = null; if (nameAttribute != null) { nameValue = nameAttribute.getValue(); } // 如果 XML 标签传入了 name 属性就直接通过注入对象的 Field 类获取注入对象的 Class if (nameValue != null) { // 获取该类属性名为 nameValue 的属性的 Field 对象 Field field = clazz.getDeclaredField(nameValue); // 直接通过 Field 对象获取注入对象的 Class Class injectObjectClazz = field.getType(); // 通过被注入对象的 Class 获取构造方法对象,并将注入对象的 Class 传入,这样就可以用这个参数是注入对象的构造方法对象来创建注入 dao 的 service 对象 Constructor constructor = clazz.getConstructor(injectObjectClazz); // 将注入对象 dao 传入构造方法中并且生成 service 的实例对象 object = constructor.newInstance(injectObject); // 如果 XML 标签没有传入 name 属性就通过 ref 属性指定的在 spring 容器中的 bean 获取注入对象的 Class } else { // 通过 spring 容器中的 bean 获取注入对象的 Class Class injectObjectClazz = injectObject.getClass(); // 由于在 spring 容器中的 bean 的类型是 UserDaoImpl ,而被注入对象中属性的类型是接口 UserDao ,所以会出现两者类型不一致,无法获取正确的构造方法的情况(构造方法参数类型是 UserDao ) Constructor constructor = clazz.getConstructor(injectObjectClazz.getInterfaces()[0]); // 将注入对象 dao 传入构造方法中并且生成 service 的实例对象 object = constructor.newInstance(injectObject); } } } /** * 使用自动装配 * 以前不使用自动装配, spring 通过扫描 XML 来获取 bean 之间的依赖关系,读取完 XML 后会去代码中扫描属性,进一步确认依赖关系,如果 XML 定义了依赖关系,但是代码中并没有这种依赖关系,就会抛出异常,上面我们模拟的过程没有写抛出异常的部分,读者可以自行添加 * * byType :直接通过代码中定义的依赖关系来进行自动装配,如果被注入对象中有注入对象这个属性,那么就说明他们之间有依赖关系,通过 Fidld 来获得注入对象的 Class 对象,再通过反射实现注入 */ // 需要在扫描完上面的所有子标签之后再判断是不是开启了自动装配,因为 spring 手动装配的优先级高于自动装配,如果上面发现 bean 标签里有子标签已经完成了手动装配,我们就不需要再执行后面的自动装配逻辑了,这里的判断标准就是被注入对象 object 是否已经被实例化 if (object == null && flag) { // 使用 byType 方式自动装配 if ( "byType" .equals(attribute.getValue())) { // 判断是否有依赖 // 获取 bean 中的所有属性的 Field 对象 Field fields[] = clazz.getDeclaredFields(); // 遍历所有的属性,查找有没有和 spring 容器中的 bean 类型一样的 for (Field field : fields) { // 得到属性类型 Class injectObjectClazz = field.getType(); /** * 由于是 byType 所以需要遍历 map 中的所有对象 * 判断对象的类型是不是和这个 injectObjectClazz 一致 */ // 记录 spring 容器中和注入对象类型匹配的个数 int count = 0; Object injectObject = null; for (String key : map.keySet()) { // 获取 spring 容器中 bean 的类型,这里为了我们样例代码演示方便就是获取的接口 Class temp = map.get(key).getClass().getInterfaces()[0]; if (temp.getName().equals(injectObjectClazz.getName())) { injectObject = map.get(key); // 记录类型一直个数 count++; } } if (count > 1) { throw new SpringException( " 需要一个 bean ,但是在容器中存在 " + count + " 个符合条件的 bean" ); } else { // 创建被注入对象 object = clazz.newInstance(); // 因为是私有属性,所以使用 field.set 注入需要开启暴力注入 field.setAccessible(true); // 实现注入 field.set(object, injectObject); } } } } // 没有子标签的情况需要单独给 bean 实例化 if (object == null) { object = clazz.newInstance(); } // 将生成的 bean 实例对象存入到 map 中 map.put(beanName, object); } } catch (Exception e) { e.printStackTrace(); } System.out.println(map); } public Object getBean(String beanName) { return map.get(beanName); } }
测试使用的三套 XML 配置文件
<?xml version = "1.0" encoding = "UTF-8" ?> <!-- 1 、哪些类需要我来管理 2 、怎么告诉我这些类 (bean 标签 ) 3 、怎么维护依赖关系 (setter 方法或构造方法 ) 4 、怎么体现 setter 或者构造方法(使用 property 或 constructor 内嵌标签) spring 就是通过 bean 标签来完成上面两个操作的 --> <beans> <!-- 下面面的 XML 配置文件的含义就是我们将 UserDaoImpl 和 UserServiceImpl 装配到 spring 容器中,将他们的 id 分别设置成 dao 和 service service 的依赖 dao ,并且通过相应的 setter 方法 / 构造方法注入 --> <!-- 告诉 sping 容器 UserDaoImpl 需要它来管理 --> <bean id = "dao" class = "priv.priv.cy.dao.UserDaoImpl" ></bean> <!-- 告诉 sping 容器 UserServiceImpl 需要它来管理 --> <bean id = "service" class = "priv.priv.cy.service.UserServiceImpl" > <!-- 注入 dao 对象的前提就是 dao 对象已经装配给 spring 容器去管理,所以要先写一个 dao 对象的 bean--> <!-- 注入方法在 setter 和 constructor 之中选一个就行,不过要在代码中把需要的构造方法或者 setter 方法编写出来 --> <!-- 表示使用 setter 方法完成注入 property 标签表示使用 setter 方法完成注意,里面有两个属性, name 和 ref name: 表示的是 setter 方法的名字, name 属性的值会去匹配将 setter 方法中的前缀 set 去掉之后,再将剩下的首字母小写得到的方法名,如果找到名字一致的注入操作就是由这个 setter 方法 因为我们一般就是用默认生成的 stter 方法名,所以这个 name 标签也可以不加,只是为了防止有需要修改 setter 方法名时,就需要特别指定一下 setter 方法了 ref: 这个指定的是要将哪个 bean 注入给 service ,后面写的是 spring 容器中 bean 的 id 所以下面这句话的意思就是我们要将 spring 容器中 id 为 dao 的 bean ,通过 service 对象中一个名为 dao 的 stter 方法将其注入给 service 对象 --> <property name = "dao" ref = "dao" ></property> </bean> </beans>
<?xml version = "1.0" encoding = "UTF-8" ?> <!-- 1 、哪些类需要我来管理 2 、怎么告诉我这些类 (bean 标签 ) 3 、怎么维护依赖关系 (setter 方法或构造方法 ) 4 、怎么体现 setter 或者构造方法(使用 property 或 constructor 内嵌标签) spring 就是通过 bean 标签来完成上面两个操作的 --> <beans> <!-- 下面面的 XML 配置文件的含义就是我们将 UserDaoImpl 和 UserServiceImpl 装配到 spring 容器中,将他们的 id 分别设置成 dao 和 service service 的依赖 dao ,并且通过相应的 setter 方法 / 构造方法注入 --> <!-- 告诉 sping 容器 UserDaoImpl 需要它来管理 --> <bean id = "dao" class = "priv.priv.cy.dao.UserDaoImpl" ></bean> <!-- 告诉 sping 容器 UserServiceImpl 需要它来管理 --> <bean id = "service" class = "priv.priv.cy.service.UserServiceImpl" > <!-- 注入 dao 对象的前提就是 dao 对象已经装配给 spring 容器去管理,所以要先写一个 dao 对象的 bean--> <!-- 注入方法在 setter 和 constructor 之中选一个就行,不过要在代码中把需要的构造方法或者 setter 方法编写出来 --> <!-- 使用构造方法注入 name: 表示的是被注入的对象中所依赖的这个对象的属性名,不是构造方法传入参数的属性名。也就是 service 对象中的 UserDao 类型的属性名为 dao 。这个 name 也可以不写, spring 会自动找到构造方法去进行注入 ref: 表示的是 bean 的 id ,也就是将 spring 容器中 id 为 dao 的这个 bean 注入到 service 中 下面这段代码的意思就是将 spring 容器中 id 为 dao 的这个 bean 通过构造方法注入到 service 中属性名为 dao 的这个属性 --> <constructor-arg name = "dao" ref = "dao" ></constructor-arg> </bean> </beans>
<?xml version = "1.0" encoding = "UTF-8" ?> <!-- 1 、哪些类需要我来管理 2 、怎么告诉我这些类 (bean 标签 ) 3 、怎么维护依赖关系 (setter 方法或构造方法 ) 4 、怎么体现 setter 或者构造方法(使用 property 或 constructor 内嵌标签) spring 就是通过 bean 标签来完成上面两个操作的 --> <beans default-autowire = "byType" > <!-- 下法注面面的 XML 配置文件的含义就是我们将 UserDaoImpl 和 UserServiceImpl 装配到 spring 容器中,将他们的 id 分别设置成 dao 和 service 并且实现自动装配( byName/byType ) --> <!-- 通过 byType 实现自动装配 这里需要注意的就是 byType 自动装配是通过 Field 类的 set 方法反射注入的,不依赖 setter 方法和构造方法,即使没有 setter 方法和构造方法依旧可以成功注入 --> <!-- 告诉 sping 容器 UserDaoImpl 需要它来管理 --> <bean id = "dao" class = "priv.priv.cy.dao.UserDaoImpl" ></bean> <!-- 告诉 sping 容器 TestDaoImpl 需要它来管理 --> <bean id = "test" class = "priv.priv.cy.dao.TestDaoImpl" ></bean> <!-- 告诉 sping 容器 UserDaoImpl1 需要它来管理 --> <!-- <bean id="dao1" class="priv.priv.cy.dao.UserDaoImpl1"></bean>--> <!-- 告诉 sping 容器 UserServiceImpl 需要它来管理 --> <bean id = "service" class = "priv.priv.cy.service.UserServiceImpl" > <!-- 手动装配优先于自动装配 --> <!-- <property name="dao" ref="dao"></property>--> </bean> </beans>
二、使用注解向容器中装配 Bean
package org.spring.util; import org.spring.annotation.Service; import java.io.File; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; /** * 实现对注解的扫描,使用注解将 bean 装配到 spring 容器 */ public class AnnotationConfigApplicationContext { /** * 真正的 AnnotationConfigApplicationContext 也有 scan 这个方法,方法原型和作用我们下面模拟的这个方法作用是一致的。 * @param basePackage */ public void scan(String basePackage) { // 获得 classpath 路径,通过这个路径获取类的全限定名,这样才可以将 bean 实例化 // 下面这个是动态获取 classpath 路径,因为项目部署到不同的服务器,它的 classpath 路径是会变化的 String rootPath = this .getClass().getResource( "/" ).getPath(); // 将包路径转换成文件路径 // 将一个 . 换成 \ . 和 \ 是一个转义字符 String basePackagePath = basePackage.replaceAll( "\\." , "\\\\" ); // 将 classpath 路径和传入的包路径拼接起来就得到了要扫描的 class 文件所在路径 String path = rootPath + "//" + basePackagePath; try { // 解决路径中文乱码问题 path = URLDecoder.decode(path, "UTF-8" ); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } File file = new File(path); // 获取当前路径的所有文件名 这个是通过递归的方法,将这个路径下面所有的文件名都取出来,不取文件夹名 String[] names = file.list(); for (String name : names) { name = name.replaceAll( ".class" , "" ); try { // 获取要交给容器的 bean 的 Class 对象,用来实例化 Class clazz = Class.forName(basePackage + "." + name); // 如果是模拟 @Autowired 使用 byType 的话,就利用反射通过 clazz 将他的所有的属性 field 对象取出,然后通过 field 获取属性的 Type ,查看 spring 容器中有没有符合条件的 bean ,有的话就取出注入,基本过程和模拟 xml 的一样 // 判断该类上的注解类型,这里只演示 @Service if (clazz.isAnnotationPresent(Service. class )) { // 获取注解的对象 Service service = (Service) clazz.getAnnotation(Service. class ); // 取得注解中的 value ,用来给 bean 设置 name System.out.println(service.value()); // 后面就是将 bean 实例化放入 factory 中去,前面对 xml 方式的模拟已经有了,这里就不再写了,直接实例化输出验证 System.out.println(clazz.newInstance()); } } catch (Exception e) { e.printStackTrace(); } } } }
其他文章:【Spring】史上最全的spring IoC使用讲解 【Spring】面试官,请别再问Spring Bean的生命周期了!