从山寨Spring中学习Spring IOC原理-byType自动装配

前言

之前的两篇博客【从山寨Spring中学习Spring IOC原理-XML-Setter】【从山寨Spring中学习Spring IOC原理-XML-Constructor】我们模拟了Spring框架下的手动装配。但是总所周知自动装配才是Spring的精髓,所以既然要山寨就不可能少了这一部份。一般来说Spring的自动装配只需要在字段声明的地方加一个@Autowired就可以了,但是在xml下,需要配置byName或者byType的方式去开启。既不需要构造方法,也不需要Setter方法。我们这一篇就是山寨一个Spring的bytype的自动装配过程,为了博客看起来比较方便,讲解的内容部分会穿插代码片,而完整的代码会贴到最后。更多Spring内容进入【Spring解读系列目录】

准备内容

还是准备一个事务接口UserDao,一个事务实现类UserDaoImpl,一个事务干扰类UserDaoImpl2,一个业务接口UserService,一个业务实现UserServiceImpl。

public interface UserDao {
    
    
    public void query();
}
public class UserDaoImpl implements UserDao{
    
    
    @Override
    public void query() {
    
    
        System.out.println("UserDaoImpl query 1");
    }
}
public class UserDaoImpl2 implements UserDao{
    
    
    @Override
    public void query() {
    
    
        System.out.println("UserDaoImpl query 2");
    }
}
public interface UserService {
    
    
    public void find();
}
public class UserServiceImpl implements UserService {
    
    
    private UserDao userDao;
    @Override
    public void find() {
    
    
        System.out.println("UserServiceImpl find()");
        userDao.query();
    }
}

思路

那么我们现在想一下,如果要完成自动装配有哪些问题。首先就是依赖,因为xml已经无法提供依赖了,所以我们要自动取判断依赖的情况。其次是报错,如果xml如果描述的和真实的情况不符需要针对这种错误抛出异常。最后添加自动装配标签,Spring的default-autowire是配置在<beans>标签里的因此,我们也需要在root标签的位置去判断是否有这个标签。直接再原来的基础上添加代码,后面就要考虑如何取到类中的依赖了。

//检查是不是配了自动装配
Attribute attributeAuto=elementRoot.attribute("default-autowire");
boolean flag=false; //自动装配标志,如果为true说明配置了
if(!Objects.isNull(attributeAuto)){
    
    
    flag=true;
}

如何判断依赖

在我们之前的手动装配的模拟程序中,我们看一个对象有没有依赖要根据两个情况:

  1. 第一看xml中标签没有子标签,因为有子标签就意味着有依赖。
  2. 第二看类中有没有依赖。

但是如果开始了自动装配,我们就可以假设<bean>标签里永远没有子标签。所以当前能够判断一个类有没有依赖的主要依据,就是这个类中有没有属性。如果有属性,就说明有依赖,需要程序进行注入。所以不管是哪个类都需要我们去判断有没有属性。如果发现有依赖就需要去map中找到对应的类型,然后给类中的字段赋值完成注入。

//如果直接走到这里说明没有子标签,但是又加了default-autowire
if(flag){
    
    
    if(attributeAuto.getValue().equals("byType")){
    
    
        //判断是否有依赖,如果有依赖,就byType的注入,因此先要拿到类里的属性
        Field[] fields=clazz.getDeclaredFields();
        //如果有依赖就要从map中找到对应类型
        for (Field field : fields) {
    
    
            //拿到要注入的属性类型
            Class injectObjClass=field.getType();
            //拿到属性以后遍历map,找到injectObjClass对应的属性类型
            for (String key: map.keySet()) {
    
    
                //从map中拿出对应类型的名字,并且和injectObjClass的名字对比
                if (map.get(key).getClass().getInterfaces()[0].getName().equals(injectObjClass.getName())){
    
    
                    injectObject=map.get(key);//拿出对象
                }
            }
			object=clazz.newInstance(); //new出实例对象
            field.setAccessible(true);
            field.set(object,injectObject); //注入
        }
    }
}

模拟报错

依赖做好了,但是Spring中有一个很常见的报错:如果发现两个类有了同一个依赖就会报错expected single matching bean but found 2。我们这里也可以把这个错误模拟出来,如果发现有两个实例同时对一个类进行注入,我们也报错。所以构建一个我们自己的报错类ConflictException,如果报错就抛出这个异常。然后修改代码。

public class ConflictException extends RuntimeException{
    
    
    public ConflictException(String message) {
    
    
        super(message);
    }
}
//如果直接走到这里说明没有子标签,但是又加了default-autowire
if(flag){
    
    
    if(attributeAuto.getValue().equals("byType")){
    
    
        //判断是否有依赖,如果有依赖,就byType的注入,因此先要拿到类里的属性
        Field[] fields=clazz.getDeclaredFields();
        int count=0;
        Object injectObject=null;
        //如果有依赖就要从map中找到对应类型
        for (Field field : fields) {
    
    ...}
            if (count>1){
    
    //说明我们找到两个类同时想要注入,Spring中会抛出异常,我们这里也抛一个异常出去
                throw new ConflictException("我们自己的异常:expected single matching bean but found 2");
            }else {
    
    
				object=clazz.newInstance(); //new出实例对象
                field.setAccessible(true);
                field.set(object,injectObject); //注入
            }
        }
    }
}

测试可用

首先我们看下我们写的异常有没有其作用,配置UserDaoImplUserDaoImpl2冲突,daodao2同时匹配UserServiceImpl里面的UserDao属性,所以一定要报错。运行测试类Test:

<beans default-autowire="byType">
    <bean id="dao" class="com.demo.dao.UserDaoImpl"></bean>
    <bean id="dao2" class="com.demo.dao.UserDaoImpl2"></bean>
    <bean id="service" class="com.demo.service.UserServiceImpl">
    </bean>
</beans>
public class Test {
    
    
    public static void main(String[] args) {
    
    
     	BeanFactoryAuto beanFactoryAuto=new BeanFactoryAuto("spring-auto.xml");
        UserService service= (UserService) beanFactoryAuto.getBean("service");
        service.find();
    }
}
运行结果:
com.demo.exception.ConflictException: 我们自己的异常:expected single matching bean but found 2
	at com.demo.beanfactory.BeanFactoryAuto.parseXml(BeanFactoryAuto.java:123)
	at com.demo.beanfactory.BeanFactoryAuto.<init>(BeanFactoryAuto.java:27)
	at com.demo.test.Test.main(Test.java:25)
Exception in thread "main" java.lang.NullPointerException
	at com.demo.test.Test.main(Test.java:27)

然后看下正常情况下,有没有给我们自动注入。打印成功,说明我们的程序已经完成了这个注入,正常执行了逻辑。

<beans default-autowire="byType">
    <bean id="dao" class="com.demo.dao.UserDaoImpl"></bean>
    <!--<bean id="dao2" class="com.demo.dao.UserDaoImpl2"></bean>-->
    <bean id="service" class="com.demo.service.UserServiceImpl">
    </bean>
</beans>
运行结果:
UserServiceImpl find()
UserDaoImpl query 1

为了更彻底的验证,我们重新创建一个逻辑SecondDao和SecondDaoImpl,让UserServiceImpl去依赖这个接口,再运行看看。

public interface SecondDao {
    
    
    public void query();
}
public class SecondDaoImpl implements SecondDao{
    
    
    @Override
    public void query() {
    
    
        System.out.println("SecondDaoImpl query()");
    }
}
public class UserServiceImpl implements UserService {
    
    
    private SecondDao userDao;
    @Override
    public void find() {
    
    
        System.out.println("UserServiceImpl find()");
        userDao.query();
    }
}
<beans default-autowire="byType">
    <bean id="dao3" class="com.demo.dao.SecondDaoImpl"></bean>
        <bean id="service" class="com.demo.service.UserServiceImpl">
    </bean>
</beans>
运行结果:
UserServiceImpl find()
SecondDaoImpl query()

一样完美的适配了,那么到这里一个简易版的byType自动注入就基本完成了。

注入优先权

自动装配的优先权是低于手动装配的。所以如果在xml中配置了<property>或者<constructor-arg>的参数,自动装配的效果应该是可以被屏蔽掉的,这里要怎么做呢。其实很简单,我们当前已经把前面两篇的程序组合在一起了,所以如果我们通过任意一种手动装配必然会给当前对象赋值,也就是object不会为null,所以只要在if(flag)前面加上判断就可以屏蔽自动装配的效果。修改一下xml运行打印正常。如果这里自动装配起作用了,一定会报我们的自定义异常的。

扫描二维码关注公众号,回复: 11921163 查看本文章
for (Iterator<Element> itSecond = elementFirstChild.elementIterator(); itSecond.hasNext();) {
    
    
    if (elementSecondChild.getName().equals("property")){
    
    
        object=clazz.newInstance();  //手动装配property赋值
    }else if (elementSecondChild.getName().equals("constructor-arg")){
    
    
        object=constructor.newInstance(injectObj); //手动装配构造参数赋值
    }
}
if (Objects.isNull(object)){
    
     //屏蔽自动装配
    if(flag){
    
    ...}
}
<beans default-autowire="byType">
    <bean id="dao" class="com.demo.dao.UserDaoImpl"></bean>
        <bean id="service" class="com.demo.service.UserServiceImpl">
        <property name="userDao" ref="dao"></property>
    </bean>
</beans>
运行结果,自动装配被屏蔽了,如果起作用报错:single matching bean but found 2
UserServiceImpl find()
UserDaoImpl query 1

总结

本篇博客完成了对Spring IOC自动注入byType模式的模拟。至于byName的模式是根据属性名来的,其实这个已经在【从山寨Spring中学习Spring IOC原理-XML-Setter】这篇中有过涉及(因为笔者偷懒,本来应该解析setter方法名的,直接解析属性名了),大家可以自己模拟或者改改笔者的程序,模拟出来这个例子。其实不止这里,因为笔者的代码只能算一个不完整的框架,如果有兴趣大家可以根据这个小框架修改,比如xml里面配置了<property>但是类里面没有要抛出异常,等等。下一篇【从山寨Spring中学习Spring IOC原理-自动装配注解】我们就去写一个有关Spring的自动注解的类,以了解在注解模式下,Spring是怎么找到那些类并实例化的。

附完整代码

这个BeanFactoryAuto自动装配的类已经整合了手动装配。

public class BeanFactoryAuto {
    
    
    //map存放类的实例
    Map<String, Object> map=new HashMap<>();
    /**
     * 工厂方法
     * @param xml
     */
    public BeanFactoryAuto(String xml) {
    
    
        parseXml(xml);
    }
    /**
     * 解析
     * @param xml
     */
    public void parseXml(String xml){
    
    
        //拿到根路径
        String path=this.getClass().getResource("/").getPath()+xml;
        File file=new File(path);
        SAXReader reader = new SAXReader();
        try {
    
    
            Document document = reader.read(file);
            Element elementRoot=document.getRootElement();
            //检查是不是配了自动装配
            Attribute attributeAuto=elementRoot.attribute("default-autowire");
            boolean flag=false; //自动装配标志,如果为true说明配置了
            if(!Objects.isNull(attributeAuto)){
    
    
                flag=true;
            }
            for (Iterator<Element> itFirst = elementRoot.elementIterator(); itFirst.hasNext();) {
    
    
                Element elementFirstChild = itFirst.next();
                Attribute attributeId=elementFirstChild.attribute("id");
                String beanName=attributeId.getValue();
                Attribute attributeClass=elementFirstChild.attribute("class");
                String clazzName=attributeClass.getValue();
                Class clazz=Class.forName(clazzName);
         		Object object=null;
                for (Iterator<Element> itSecond = elementFirstChild.elementIterator(); itSecond.hasNext();) {
    
    
                    Element elementSecondChild =itSecond.next();
                    if (elementSecondChild.getName().equals("property")){
    
     //手动装配property逻辑
                        object=clazz.newInstance();
                        Object injectObj=map.get(elementSecondChild.attribute("ref").getValue());
                        String nameValue=elementSecondChild.attribute("name").getValue();
                        Field field=clazz.getDeclaredField(nameValue);
                        field.setAccessible(true);
                        field.set(object,injectObj);
                    }else if (elementSecondChild.getName().equals("constructor-arg")){
    
     //手动装配构造方法逻辑
                        String refValue=elementSecondChild.attribute("ref").getValue();
                        Object injectObj=map.get(refValue);
                        Class injectObjClazz=injectObj.getClass();
                        String nameValue=elementSecondChild.attribute("name").getValue();
                        Field field=clazz.getDeclaredField(nameValue);
                        Constructor constructor=clazz.getConstructor(field.getType());
                        object=constructor.newInstance(injectObj);
                    }
                }
                if (Objects.isNull(object)){
    
     //屏蔽自动装配
                    //如果直接走到这里说明没有子标签,但是又加了default-autowire
                    if(flag){
    
    
                        if(attributeAuto.getValue().equals("byType")){
    
    
                            //判断是否有依赖,如果有依赖,就byType的注入,因此先要拿到类里的属性
                            Field[] fields=clazz.getDeclaredFields();
                            int count=0;
                            Object injectObject=null;
                            //如果有依赖就要从map中找到对应类型
                            for (Field field : fields) {
    
    
                                //拿到要注入的属性类型
                                Class injectObjClass=field.getType();
                                //拿到属性以后遍历map,找到injectObjClass对应的属性类型
                                for (String key: map.keySet()) {
    
    
                                    //从map中拿出对应类型的名字,并且和injectObjClass的名字对比
                                    if (map.get(key).getClass().getInterfaces()[0].getName().equals(injectObjClass.getName())){
    
    
                                        //每找到一个就记录一次,这里模仿的就是Spring注入冲突
                                        count++;
                                        injectObject=map.get(key);
                                    }
                                }
                                if (count>1){
    
    //说明我们找到两个类同时想要注入,Spring中会抛出异常,我们这里也抛一个异常出去
                                    throw new ConflictException("我们自己的异常:expected single matching bean but found 2");
                                }else {
    
    
                                    object=clazz.newInstance(); //new出实例对象
                                    field.setAccessible(true);
                                    field.set(object,injectObject);
                                }
                            }
                        }
                    }
                }
                if(object==null){
    
    //没有子标签,意味着没有依赖所以new出来
                    object=clazz.newInstance();
                }
                map.put(beanName,object);
            }
            System.out.println(map.toString());
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
    /**
     * 获取对应的bean
     * @param bean
     * @return
     */
    public Object getBean(String bean){
    
    
        return map.get(bean);
    }
}

猜你喜欢

转载自blog.csdn.net/Smallc0de/article/details/108281921