【Spring】手写模拟spring IoC容器

自定义异常

/**
* 自定义异常
*/
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 {
           
// 生成xmlDOM
            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();
               
// 获取beanclass对象
                Class clazz = Class.forName(clazzName);
 
               
// 生成对应bean的对象引用
                Object object = null;
 
 
               
/**
                 * 2、维护依赖关系
                 * 看这个对象有没有依赖(判断xmlbean标签中是否有property这个子标签/或者判断类是否有属性)
                 * 如果有则注入
                 */
                for (Iterator<Element> itSecond = elementFirstChil.elementIterator(); itSecond.hasNext(); ) {
                   
/**
                     * 得到refvalue,通过value得到要注入的对象(从map中取得)
                     * 得到name的值,根据这个值回去一个Filed对象(Filed对象用来取得一个类中的属性)   这里为了简单默认使用set方法来进行注入,所以要取name
                     * 通过filedset方法来注入对象
                     */
                    // 取得二级子标签很替
                    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对象daoset方法名与service对象中UserDao属性名一致,都是dao
                        String nameValue = elementSecondChil.attribute("name").getValue();
                       
// 获取该类属性名为nameValue的属性的Field对象,来进行注入
                        Field field = clazz.getDeclaredField(nameValue);
                       
// 因为这个属性是私有的,所以要开启暴力反射,将Accessible设置为true
                        field.setAccessible(true);
                       
// 将要注入的对象注入到object中,这里object表示的是被注入的对象,也就是serviceinjectObject表示的是注入的对象,也就是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传入,这样就可以用这个参数是注入对象的构造方法对象来创建注入daoservice对象
                            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或者构造方法(使用propertyconstructor内嵌标签)
 
    spring就是通过bean标签来完成上面两个操作的
-->
<beans>
    <!--
        下面面的XML配置文件的含义就是我们将UserDaoImplUserServiceImpl装配到spring容器中,将他们的id分别设置成daoservice
        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-->
        <!--注入方法在setterconstructor之中选一个就行,不过要在代码中把需要的构造方法或者setter方法编写出来-->
 
       
<!--
            表示使用setter方法完成注入
            property标签表示使用setter方法完成注意,里面有两个属性,nameref
            name:表示的是setter方法的名字,name属性的值会去匹配将setter方法中的前缀set去掉之后,再将剩下的首字母小写得到的方法名,如果找到名字一致的注入操作就是由这个setter方法
                 因为我们一般就是用默认生成的stter方法名,所以这个name标签也可以不加,只是为了防止有需要修改setter方法名时,就需要特别指定一下setter方法了
            ref:这个指定的是要将哪个bean注入给service,后面写的是spring容器中beanid
 
            所以下面这句话的意思就是我们要将spring容器中iddaobean,通过service对象中一个名为daostter方法将其注入给service对象
        -->
        <property name="dao" ref="dao"></property>
 
 
 
   
</bean>
</beans>

 
<?xml version="1.0" encoding="UTF-8"?>
<!--
    1、哪些类需要我来管理
    2、怎么告诉我这些类(bean标签)
    3、怎么维护依赖关系(setter方法或构造方法)
    4、怎么体现setter或者构造方法(使用propertyconstructor内嵌标签)
 
    spring就是通过bean标签来完成上面两个操作的
-->
<beans>
    <!--
        下面面的XML配置文件的含义就是我们将UserDaoImplUserServiceImpl装配到spring容器中,将他们的id分别设置成daoservice
        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-->
        <!--注入方法在setterconstructor之中选一个就行,不过要在代码中把需要的构造方法或者setter方法编写出来-->
 
       
<!--
            使用构造方法注入
            name:表示的是被注入的对象中所依赖的这个对象的属性名,不是构造方法传入参数的属性名。也就是service对象中的UserDao类型的属性名为dao。这个name也可以不写,spring会自动找到构造方法去进行注入
            ref:表示的是beanid,也就是将spring容器中iddao的这个bean注入到service
 
            下面这段代码的意思就是将spring容器中iddao的这个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或者构造方法(使用propertyconstructor内嵌标签)
 
    spring就是通过bean标签来完成上面两个操作的
-->
<beans default-autowire="byType">
    <!--
        下法注面面的XML配置文件的含义就是我们将UserDaoImplUserServiceImpl装配到spring容器中,将他们的id分别设置成daoservice
        并且实现自动装配(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 {
               
// 获取要交给容器的beanClass对象,用来实例化
               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的生命周期了!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章