手写实现自定义简易版Spring (实现IoC 和 AOP)

Posted 丿涛哥哥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手写实现自定义简易版Spring (实现IoC 和 AOP)相关的知识,希望对你有一定的参考价值。

手写实现自定义简易版Spring (实现IoC 和 AOP)

源码地址点这里

1、 银行转账案例界面

Spring_61

2、 银行转账案例表结构

Spring_62

3、 银行转账案例代码调用关系

Spring_63

4、 银行转账案例关键代码

TransferServlet

package com.tao.servlet;

import com.tao.service.impl.TransferServiceImpl;
import com.tao.utils.JsonUtils;
import com.tao.pojo.Result;
import com.tao.service.TransferService;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author tao
 */
@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));
    }
}

TransferService接口及实现类

package com.tao.service;
/**
* @author tao
*/
public interface TransferService {
	void transfer(String fromCardNo,String toCardNo,int money) throws Exception;
}
package com.tao.service.impl;

import com.tao.dao.AccountDao;
import com.tao.dao.impl.JdbcAccountDaoImpl;
import com.tao.pojo.Account;
import com.tao.service.TransferService;

/**
 * @author tao
 */
public class TransferServiceImpl implements TransferService {
    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(from);
        accountDao.updateAccountByCardNo(to);
    }
}

AccountDao层接口及基于Jdbc的实现类

package com.tao.dao;

import com.tao.pojo.Account;

/**
* @author tao
*/
public interface AccountDao {
    
	Account queryAccountByCardNo(String cardNo) throws Exception;
    
	int updateAccountByCardNo(Account account) throws Exception;
}

JdbcAccountDaoImpl(Jdbc技术实现Dao层接口)

package com.tao.dao.impl;

import com.tao.pojo.Account;
import com.tao.dao.AccountDao;
import com.tao.utils.DruidUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

/**
 * @author tao
 */
public class JdbcAccountDaoImpl implements AccountDao {
    
    @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 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;
    }
}

5、 银行转账案例代码问题分析

Spring_64

问题一:在上述案例实现中,service 层实现类在使用 dao 层对象时,直接在 TransferServiceImpl 中通过 AccountDao accountDao = new JdbcAccountDaoImpl() 获得了 dao层对象,然而一个 new 关键字却将 TransferServiceImpl 和 dao 层具体的一个实现类 JdbcAccountDaoImpl 耦合在了一起,如果说技术架构发生一些变动,dao 层的实现要使用其它技术, 比如 Mybatis,思考切换起来的成本?每一个 new 的地方都需要修改源代码,重新编译,面向接口开发的意义将大打折扣?

问题二:service 层代码没有竟然还没有进行事务控制 ?!如果转账过程中出现异常,将可能导致数据库数据错乱,后果可能会很严重,尤其在⾦融业务。

6、 问题解决思路

针对问题一思考:

实例化对象的方式除了 new 之外,还有什么技术?反射 (需要把类的全限定类名配置在xml中)

考虑使用设计模式中的工厂模式解耦合,另外项⽬中往往有很多对象需要实例化,那就在工厂中使用反射技术实例化对象,工厂模式很合适

Spring_65

更进一步,代码中能否只声明所需实例的接口类型,不出现 new 也不出现工厂类的字眼,如下图? 能!声明一个变量并提供 set 方法,在反射的时候将所需要的对象注⼊进去吧

Spring_66

针对问题二思考:

service 层没有添加事务控制,怎么办?没有事务就添加上事务控制,⼿动控制 JDBC 的 Connection 事务,但要注意将Connection和当前线程绑定(即保证一个线程只有一个 Connection,这样操作才针对的是同一个 Connection,进而控制的是同一个事务)

Spring_67

7、 案例代码改造

针对问题一的代码改造

beans.xml

<?xml version="1.0" encoding="UTF-8" ?>
<beans>
    <bean id="transferService" class="com.tao.service.impl.TransferServiceImpl">
    	<property name="AccountDao" ref="accountDao"></property>
    </bean>
    	<bean id="accountDao" class="com.tao.dao.impl.JdbcAccountDaoImpl">
    </bean>
</beans>

增加 BeanFactory.java

package com.tao.factory;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author tao
 */
public class BeanFactory {
    /**
     * 工厂类的两个任务
     * 任务一:加载解析xml,读取xml中的bean信息,通过反射技术实例化bean对象,然后放⼊map待用
     * 任务二:提供接口方法根据id从map中获取bean(静态方法)
     */
    private static Map<String,Object> map = new HashMap<>();
        static {
            InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
            SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read(resourceAsStream);
            Element rootElement = document.getRootElement();
            List<Element> list = rootElement.selectNodes("//bean");
            // 实例化bean对象
            for (int i = 0; i < list.size(); i++) {
                Element element = list.get(i);
                String id = element.attributeValue("id");
                String clazz = element.attributeValue("class");
                Class<?> aClass = Class.forName(clazz);
                Object o = aClass.newInstance();
                map.put(id,o);
            }
            // 维护bean之间的依赖关系
            List<Element> propertyNodes = rootElement.selectNodes("//property");
            for (int i = 0; i < propertyNodes.size(); i++) {
            	Element element = propertyNodes.get(i);
                // 处理property元素
                String name = element.attributeValue("name");
                String ref = element.attributeValue("ref");

                String parentId = element.getParent().attributeValue("id");
                Object parentObject = map.get(parentId);
                Method[] methods = parentObject.getClass().getMethods();
                for (int j = 0; j < methods.length; j++) {
                    Method method = methods[j];
                    if(("set" + name).equalsIgnoreCase(method.getName())){
                        // bean之间的依赖关系(注⼊bean)
                        Object propertyObject = map.get(ref);
                        method.invoke(parentObject,propertyObject);
                    }
                }
                // 维护依赖关系后重新将bean放⼊map中
                map.put(parentId,parentObject);
            }
        } catch (DocumentException e) {
        e.printStackTrace();
        } catch (ClassNotFoundException e) {
        e.printStackTrace();
        } catch (IllegalAccessException e) {
        e.printStackTrace();
        } catch (InstantiationException e) {
        e.printStackTrace();
        } catch (InvocationTargetException e) {
        e.printStackTrace();
        }
    }
    
    public static Object getBean(String id) {
    	return map.get(id);
    }
}

修改 TransferServlet

Spring_68

修改 TransferServiceImpl

Spring_69

针对问题二的改造

增加 ConnectionUtils

package com.tao.utils;

import java.sql.Connection;
import java.sql.SQLException;

/**
 * @author tao
 */
public class ConnectionUtils {
    
    /*private ConnectionUtils() {
    }
    
    private static ConnectionUtils connectionUtils = new ConnectionUtils();
    
    public static ConnectionUtils getInstance() {
    	return connectionUtils;
    }*/

    private ThreadLocal<Connection> threadLocal = new ThreadLocal<>(); //存储当前线程的连接

    /**
     * 从当前线程获取连接
     */
    public Connection getCurrentThreadConn() throws SQLException {
        /**
         * 判断当前线程中是否已经绑定连接,如果没有绑定,需要从连接池获取一个连接绑定到当前线程
         */
        Connection connection = threadLocal.get();
        if(connection == null) {
            // 从连接池拿连接并绑定到线程
            connection = DruidUtils.getInstance().getConnection();
            // 绑定到当前线程
            threadLocal.set(connection);
        }
        return connection;
    }
}

增加 TransactionManager 事务管理器类

package com.tao.utils;

import java.sql.SQLException;

/**
 * @author tao
 */
public class TransactionManager {
    private ConnectionUtils connectionUtils;
    
    public void setConnectionUtils(ConnectionUtils connectionUtils) {
    	this.connectionUtils = connectionUtils;
    }
    
    // 开启事务
    public void beginTransaction() throws SQLException {
    	connectionUtils.getCurrentThreadConn().setAutoCommit(false);
    }
    
    // 提交事务
    public void commit() throws SQLException {
    	connectionUtils.getCurrentThreadConn().commit();
    以上是关于手写实现自定义简易版Spring (实现IoC 和 AOP)的主要内容,如果未能解决你的问题,请参考以下文章

手写一个简易的IOC

Spring IOC :相关接口分析手写简易 Spring IOC

Spring IOC :相关接口分析手写简易 Spring IOC

Spring IOC :相关接口分析手写简易 Spring IOC

Spring系列之手写一个SpringMVC

手写Spring MVC框架 实现简易版mvc框架