自定义异常
/** * 自定义异常 */ 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的生命周期了!