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

本文通过一个银行转账案例,详细分析了Spring中IoC和AOP的实现原理。首先展示了原始代码存在的问题,如过度耦合和缺少事务控制。接着,通过引入BeanFactory、ProxyFactory和事务管理器,实现了IoC和AOP的改进,降低了耦合度并加入了事务管理。最后,展示了改造后的代码,包括使用代理工厂增强事务控制,并利用自定义注解进行功能扩展。
摘要由CSDN通过智能技术生成

手写实现自定义简易版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();
    }
    
    // 回滚事务
    public void rollback() throws SQLException {
    	connectionUtils.getCurrentThreadConn().rollback();
    }
}

增加 ProxyFactory 代理工厂类

package com.tao.factory;

import com.tao.utils.TransactionManager;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
* @author tao
*/
public class ProxyFactory {
    private TransactionManager transactionManager;
        public void setTransactionManager(TransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }
    
    public Object getProxy(Object target) {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object result = null;
                try{
                    // 开启事务
                    transactionManager.beginTransaction();
                    // 调用原有业务逻辑
                    result = method.invoke(target,args);
                    // 提交事务
                    transactionManager.commit();
                }catch(Exception e) {
                    e.printStackTrace();
                    // 回滚事务
                    transactionManager.rollback();
                    // 异常向上抛出,便于servlet中捕获
                    throw e.getCause();
                }
                return result;
            }
        });
    }
}

修改 beans.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!--跟标签beans,⾥面配置一个⼜一个的bean⼦标签,每一个bean⼦标签都代表一个类的配置-->
<beans>
    <!--id标识对象,class是类的全限定类名-->
    <bean id="accountDao" class="com.tao.dao.impl.JdbcAccountDaoImpl">
    	<property name="ConnectionUtils" ref="connectionUtils"/>
    </bean>
    
    <bean id="transferService" class="com.tao.service.impl.TransferServiceImpl">
        <!--set+ name 之后锁定到传值的set方法了,通过反射技术可以调用该方法传⼊对应的值-->
        <property name="AccountDao" ref="accountDao"></property>
    </bean>
    
    <!--配置新增的三个Bean-->
    <bean id="connectionUtils" class="com.tao.utils.ConnectionUtils"></bean>
    
    <!--事务管理器-->
    <bean id="transactionManager" class="com.tao.utils.TransactionManager">
    	<property name="ConnectionUtils" ref="connectionUtils"/>
    </bean>
    
    <!--代理对象工厂-->
    <bean id="proxyFactory" class="com.tao.factory.ProxyFactory">
    	<property name="TransactionManager" ref="transactionManager"/>
    </bean>
</beans>

修改 JdbcAccountDaoImpl

package com.tao.dao.impl;

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

/**
 * @author tao
 */
public class JdbcAccountDaoImpl implements AccountDao {
    private ConnectionUtils connectionUtils;
    
    public void setConnectionUtils(ConnectionUtils connectionUtils) {
    	this.connectionUtils = connectionUtils;
    }
    
    @Override
    public Account queryAccountByCardNo(String cardNo) throws Exception {
        //从连接池获取连接
        // Connection con = DruidUtils.getInstance().getConnection();
        Connection con = connectionUtils.getCurrentThreadConn();
        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();
        Connection con = connectionUtils.getCurrentThreadConn();
        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;
    }
}

修改 TransferServlet

package com.tao.servlet;

import com.tao.factory.BeanFactory;
import com.tao.factory.ProxyFactory;
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();
    //private TransferService transferService = (TransferService)BeanFactory.getBean("transferService");
    // 从工厂获取委托对象(委托对象是增强了事务控制的功能)
    // ⾸先从BeanFactory获取到proxyFactory代理工厂的实例化对象
    private ProxyFactory proxyFactory = (ProxyFactory)BeanFactory.getBean("proxyFactory");
    
    private TransferService transferService = (TransferService)proxyFactory.getJdkProxy(BeanFactory.getBean("transferService")) ;
    
    @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));
    }
}

后续在功能实现上增加了自定义注解的方式

源码地址点这里

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值