美文网首页
xml方式自定义实现Ioc容器

xml方式自定义实现Ioc容器

作者: 喊我小王吧 | 来源:发表于2020-11-20 10:42 被阅读0次

    @[TOC]

    xml方式自定义实现Ioc容器

    使用xml实现自定义简单的Ioc容器

    前言

    平时开发过程中,我们都是使用Spring来进行开发,Spring核心的Ioc容器帮助我们去创建对象这一过程被称作控制反转也叫Ioc
    在实例化一个对象时候,这个对象中用到一个对象类型的属性,容器把这个对象注入到实例化对象的过程被称作依赖注入简称DI

    Ioc和DI说的是一个事情,针对的侧重点不同,IOC是站在容器角度创建对象,DI是站在使用的角度,注入使用对象;

    没有IOC容器的时候

    在这里插入图片描述

    模拟银行转账例子

    转账接口

    public interface AccountDao {
    
        Account queryAccountByCardNo(String cardNo) throws Exception;
    
        int updateAccountByCardNo(Account account) throws Exception;
    }
    
    

    接口实现类

    public class JdbcAccountDaoImpl implements AccountDao {
    
        public void init() {
            System.out.println("初始化方法.....");
        }
    
        public void destory() {
            System.out.println("销毁方法......");
        }
    
        @Override
        public Account queryAccountByCardNo(String cardNo) throws Exception {
            //从连接池获取连接
            Connection con = DruidUtils.getInstance().getConnection();
            String sql = "select * from account where cardNo=?";
            PreparedStatement preparedStatement = con.prepareStatement(sql);
            preparedStatement.setString(1,cardNo);
            ResultSet resultSet = preparedStatement.executeQuery();
    
            Account account = new Account();
            while(resultSet.next()) {
                account.setCardNo(resultSet.getString("cardNo"));
                account.setName(resultSet.getString("name"));
                account.setMoney(resultSet.getInt("money"));
            }
    
            resultSet.close();
            preparedStatement.close();
            //con.close();
    
            return account;
        }
    
        @Override
        public int updateAccountByCardNo(Account account) throws Exception {
    
            // 从连接池获取连接
            // 改造为:从当前线程当中获取绑定的connection连接
            Connection con = DruidUtils.getInstance().getConnection();
            String sql = "update account set money=? where cardNo=?";
            PreparedStatement preparedStatement = con.prepareStatement(sql);
            preparedStatement.setInt(1,account.getMoney());
            preparedStatement.setString(2,account.getCardNo());
            int i = preparedStatement.executeUpdate();
    
            preparedStatement.close();
            //con.close();
            return i;
        }
    }
    
    

    业务接口

    public interface TransferService {
    
        void transfer(String fromCardNo,String toCardNo,int money) throws Exception;
    }
    
    

    实现类

    public class TransferServiceImpl implements TransferService {
    
        // 1 原始的new 方法创建dao接口实现类对象
       private AccountDao accountDao = new JdbcAccountDaoImpl();
    
        @Override
        public void transfer(String fromCardNo, String toCardNo, int money) throws Exception {
    
            Account from = accountDao.queryAccountByCardNo(fromCardNo);
                Account to = accountDao.queryAccountByCardNo(toCardNo);
    
                from.setMoney(from.getMoney()-money);
                to.setMoney(to.getMoney()+money);
    
                accountDao.updateAccountByCardNo(to);
                //int c = 1/0;
                accountDao.updateAccountByCardNo(from);
    
        }
    }
    
    

    controller层

    @WebServlet(name="transferServlet",urlPatterns = "/transferServlet")
    public class TransferServlet extends HttpServlet {
    
        // 1. 实例化service层对象
        private TransferService transferService = new TransferServiceImpl();
    
    
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            doPost(req,resp);
        }
    
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
            // 设置请求体的字符编码
            req.setCharacterEncoding("UTF-8");
    
            String fromCardNo = req.getParameter("fromCardNo");
            String toCardNo = req.getParameter("toCardNo");
            String moneyStr = req.getParameter("money");
            int money = Integer.parseInt(moneyStr);
    
            Result result = new Result();
    
            try {
    
                // 2. 调用service层方法
                transferService.transfer(fromCardNo,toCardNo,money);
                result.setStatus("200");
            } catch (Exception e) {
                e.printStackTrace();
                result.setStatus("201");
                result.setMessage(e.toString());
            }
    
            // 响应
            resp.setContentType("application/json;charset=utf-8");
            resp.getWriter().print(JsonUtils.object2Json(result));
        }
    }
    
    

    可以看出来在controller层 new业务层的对象,new 业务层的对象中已经new出来dao层的实现类

    业务层和dao层通过new关键字连接起来,耦合高

    在这里插入图片描述

    使用Ioc容器情况下

    在这里插入图片描述

    如图可以看到,Ioc容器讲过AB对象的示例存储起来,main函数使用AB对象的时候,直接在Ioc容器中获取;

    基于这个理解,我们可以实现自己的Ioc容器;

    首先呢,我们新建自己的xml,用来配置自己的需要一些示例话的bean也就是对象

    <?xml version="1.0" encoding="UTF-8"?>
    <beans>
        <bean id="accountDao" class="com.udeam.edu.dao.impl.JdbcAccountDaoImpl">
    
        </bean>
    
        <!--    TransferServiceImpl service-->
        <bean id="transferService" class="com.udeam.edu.service.impl.TransferServiceImpl">
    </property>
        </bean>
    </beans>
    
    

    创建dao层实现类和业务层实现类,并且过自己的Id从容器中获取;

    创建BeanFactorys类来解析xml,实例化配置的对象

    /**
     * 工厂Bean
     */
    public class BeanFactorys {
    
        private final static Map<String, Object> iocMap = new HashMap<>();
    
        static {
            // 1 读取解析beans.xml  通过反射技术,生产bean对象,并将其存在map中
    
            InputStream resourceAsStream = BeanFactorys.class.getClassLoader().getResourceAsStream("beans.xml");
    
            //得到一个 文档对象
            try {
                Document read = new SAXReader().read(resourceAsStream);
                //获取跟对象
                Element rootElement = read.getRootElement();
    
                /**
                 * xpath表达式 用法
                 *   // 从匹配选择的当前节点选择文档中的节点,而不考虑他们的位置
                 *   / 从根节点获取
                 *  . 选取当前节点
                 *  .. 选取当前节点的父节点
                 *  @ 选取属性
                 *
                 */
                // //表示读取任意位置的bean标签
                List<Element> list = rootElement.selectNodes("//bean");
    
                if (Objects.isNull(list) || list.size() == 0) {
                    throw new RuntimeException("无此bean标签");
                }
    
                list.forEach(x -> {
                    //获取Id
                    String id = x.attributeValue("id"); //accountDao
                    //获取权限定命名
                    String clasz = x.attributeValue("class"); //com.udeam.edu.dao.impl.JdbcAccountDaoImpl
                    System.out.println(id + " ---> " + clasz);
                    //通过反射创建对象
                    try {
                        Object o = Class.forName(clasz).newInstance();
                        //存入ioc容器
                        iocMap.put(id, o);
    
                    } catch (InstantiationException e) {
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
    
    
                });
     
    
            } catch (DocumentException e) {
                e.printStackTrace();
            }
    
    
            // 2 对外提供获取示例对象接口
    
    
        }
    
        /**
         * 对外提供获取bean接口
         *
         * @param id
         * @return
         */
        public static Object getBean(String id) {
            return iocMap.get(id);
        }
    }
    
    

    可以看到在类加载的时候通过解析xml获取其中的bean 的class权限定名,通过反射创建对象,存储在map中,id作为key;

    然后我们需要在实现类和控制层去替换这个new关键字创建的对象

      private TransferServiceImpl transferService = (TransferServiceImpl) BeanFactorys.getBean("transferService");
    
    
        private AccountDao accountDao = (AccountDao) BeanFactorys.getBean("accountDao");
    
    

    但是这样子实现还是有点不优雅 还是有=连接;

    =号去掉

    private AccountDao accountDao
    
     accountDao.queryAccountByCardNo(fromCardNo);
    

    此时,没有赋值操作,这儿会默认null 空出现空指针异常;
    为此,我们需要进行设置值,可以通过set,构造参数等设置值;

    可以在xml中使用属性set设置值

    在xml中定义一个 对象属性 property 设置名字name和ref引用对象

        <bean id="transferService" class="com.udeam.edu.service.impl.TransferServiceImpl">
            <property name="AccountDao" ref="accountDao"></property>
        </bean>
    
    

    在刚才的BeanFactorys中添加解析方法

    在解析xml完 实例化对象到Ioc容器之后,来解析property属性

       //获取所有properties 属性 并且set设置值
                List<Element> prList = rootElement.selectNodes("//property");
    
                prList.forEach(y -> {
                    //获取 property 属性name值
                    String name = y.attributeValue("name"); //   <property name="setAccountDao" ref = "accountDao"></property>
                    String ref = y.attributeValue("ref");
                    //获取父节点id
                    Element parent = y.getParent();
                    //获取父节点id
                    String id = parent.attributeValue("id");
                    //维护对象依赖关系
                    Object o = iocMap.get(id);
                    //找到所有方法
                    Method[] methods = o.getClass().getMethods();
                    for (int i = 0; i < methods.length; i++) {
                        //方法就是set属性反方
                        if (methods[i].getName().equalsIgnoreCase("set" + name)) {
                            try {
                                //set设置对象
                                methods[i].invoke(o, iocMap.get(ref));
                            } catch (IllegalAccessException e) {
                                e.printStackTrace();
                            } catch (InvocationTargetException e) {
                                e.printStackTrace();
                            }
    
                            //set之后重新赋值
                            iocMap.put(id,o);
                        }
    
    
                    }
    
    
                });
    
    

    然后TransferServiceImpl类中添加一个set方法设置我们需要设置的值

      private AccountDao accountDao;
    
        public void setAccountDao(AccountDao accountDao) {
            this.accountDao = accountDao;
        }
    

    xml上中获取这个set方法,然后利用set + 获取的property属性name值 进行判断,然后反射设置值

         if (methods[i].getName().equalsIgnoreCase("set" + name)) {
                            try {
                                //set设置对象
                                methods[i].invoke(o, iocMap.get(ref));
                            } catch (IllegalAccessException e) {
                                e.printStackTrace();
                            } catch (InvocationTargetException e) {
                                e.printStackTrace();
                            }
    
                            //set之后重新赋值
                            iocMap.put(id,o);
                        }
    

    这个过程可以看做是简单的set注入方式,类似于Spring中的set注入;

    IoC解决了什么问题

    IoC解决对象之间的耦合问题

    我们不⽤⾃⼰去new对象了,⽽是由IoC容器(Spring框架)去帮助我们实例化对
    象并且管理它,我们需要使⽤哪个对象,去问IoC容器要即可

    控制反转

    控制:指的是对象创建(实例化、管理)的权利
    反转:控制权交给外部环境了(spring框架、IoC容器)

    用到的依赖

      <!-- mysql数据库驱动包 -->
        <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <version>5.1.35</version>
        </dependency>
        <!--druid连接池-->
        <dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>druid</artifactId>
          <version>1.1.21</version>
        </dependency>
    
        <!-- servlet -->
        <dependency>
          <groupId>javax.servlet</groupId>
          <artifactId>javax.servlet-api</artifactId>
          <version>3.1.0</version>
          <scope>provided</scope>
        </dependency>
    
        <!-- jackson依赖 -->
        <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-databind</artifactId>
          <version>2.9.6</version>
        </dependency>
    
        <!--dom4j依赖-->
        <dependency>
          <groupId>dom4j</groupId>
          <artifactId>dom4j</artifactId>
          <version>1.6.1</version>
        </dependency>
        <!--xpath表达式依赖 为了快速定位xml元素-->
        <dependency>
          <groupId>jaxen</groupId>
          <artifactId>jaxen</artifactId>
          <version>1.1.6</version>
        </dependency>
    
    

    代码

    传送门

    相关文章

      网友评论

          本文标题:xml方式自定义实现Ioc容器

          本文链接:https://www.haomeiwen.com/subject/aqkdiktx.html