分析并手写实现IOC

业务场景

模拟银行转账业务
1. 页面

2. 数据表结构
CREATE TABLE `account` (
  `name` varchar(255) DEFAULT NULL,
  `cardNo` varchar(255) DEFAULT NULL,
  `money` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8
3. 表数据

4. 基本代码

TransferServlet.java

@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及实现类

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

//    private AccountDao accountDao = new JdbcAccountDaoImpl();
//    private AccountDao accountDao = (AccountDao) BeanFactory.getBean("accountDao");

    private AccountDao accountDao;

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

    private TransactionManager transactionManager;

    public void setTransactionManager(TransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    @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);
            accountDao.updateAccountByCardNo(from);
    }
}

AccountDao及实现类

public interface AccountDao {

    Account queryAccountByCardNo(String cardNo) throws Exception;

    int updateAccountByCardNo(Account account) throws Exception;
}
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;
    }
}

工具类 DruidUtils

public class DruidUtils {

    private DruidUtils(){
    }

    private static DruidDataSource druidDataSource = new DruidDataSource();


    static {
        druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
        druidDataSource.setUrl("jdbc:mysql://localhost:3306/bank");
        druidDataSource.setUsername("root");
        druidDataSource.setPassword("root");

    }

    public static DruidDataSource getInstance() {
        return druidDataSource;
    }

}

JsonUtils

public class JsonUtils {

    private static final ObjectMapper MAPPER = new ObjectMapper();

    /**
     * 将对象转换成json字符串。
     * @param data
     * @return
     */
    public static String object2Json(Object data) {
    	try {
			String string = MAPPER.writeValueAsString(data);
			return string;
		} catch (JsonProcessingException e) {
			e.printStackTrace();
		}
    	return null;
    }
    
    /**
     * 将json结果集转化为对象
     * 
     * @param jsonData json数据
     * @param beanType 对象中的object类型
     * @return
     */
    public static <T> T json2Pojo(String jsonData, Class<T> beanType) {
        try {
            T t = MAPPER.readValue(jsonData, beanType);
            return t;
        } catch (Exception e) {
        	e.printStackTrace();
        }
        return null;
    }
    
    /**
     * 将json数据转换成pojo对象list
     * @param jsonData
     * @param beanType
     * @return
     */
    public static <T>List<T> json2List(String jsonData, Class<T> beanType) {
    	JavaType javaType = MAPPER.getTypeFactory().constructParametricType(List.class, beanType);
    	try {
    		List<T> list = MAPPER.readValue(jsonData, javaType);
    		return list;
		} catch (Exception e) {
			e.printStackTrace();
		}
    	
    	return null;
    }
    
}

实体类 Result

public class Result {

    private String status;
    private String message;

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    @Override
    public String toString() {
        return "Result{" +
                "status='" + status + '\'' +
                ", message='" + message + '\'' +
                '}';
    }
}

Account

public class Account {

    private String cardNo;
    private String name;
    private int money;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getMoney() {
        return money;
    }

    public void setMoney(int money) {
        this.money = money;
    }

    public String getCardNo() { return cardNo; }

    public void setCardNo(String cardNo) { this.cardNo = cardNo;}

    @Override
    public String toString() {
        return "Account{" +
                "cardNo='" + cardNo + '\'' +
                ", name='" + name + '\'' +
                ", money=" + money +
                '}';
    }
}
5. 代码分析
  • 代码调用关系
  • 代码分析
6. 问题分析
  • 问题一 代码耦合 针对问题一思考: 实例化对象的⽅式除了 new 之外,还有什么技术?
    1. 反射 (需要把类的全限定类名配置在xml中)
    2. 使用工厂模式来通过反射生产对象
    3. 更进一步,代码中能否只需要声明所需实例的接口类型,不出现new关键字,也不出现工厂类的字眼?

解决思路: 1.创建一个放置所有类的全限定名的配置类 beans.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!--根标签beans,里面配置bean子标签,每一个bean子标签都代表一个类的配置-->
<beans>
    <bean id="accountDao" class="com.example.dao.impl.JdbcAccountDaoImpl">
    </bean>
    <bean id="transferService" class="com.example.service.impl.TransferServiceImpl">
    </bean>
</beans>
  • 注 这里有一个问题 accountDao的bean标签必须放在transferService标签的上边,否则如果先初始化service,调用newInstance()的时候,就会去调用getBean()去获取,此时map中并没有dao的实例。

2.创建配置类,解析并存储所有实例化之后的bean,并提供获取方法

public class BeanFactory {

    private static Map<String, Object> map = new HashMap<>();
    /**
     * 解析配置文件  创建所有的bean
     */
    static {
        //读取解析beans.xml
        InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
        SAXReader saxReader = new SAXReader();
        Document document = null;
        try {
            document = saxReader.read(resourceAsStream);
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        //获取根节点  beans
        Element rootElement = document.getRootElement();
        //获取所有的bean标签
        List<Element> beanLst = rootElement.selectNodes("//bean");
        for (Element bean : beanLst) {
            //获取每个标签的id和class
            String id = bean.attributeValue("id");
            String clazz = bean.attributeValue("class");
            //反射创建class实例
            try {
                Class<?> instance = Class.forName(clazz);
                //将实例存储在map中
                map.put(id, instance.newInstance());
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 提供getBean方法
     */
    public static Object getBean(String id) {
        return map.get(id);
    }

}

3.修改service中和servlet中的引用 从工厂中获取 servlet

@WebServlet(name="transferServlet",urlPatterns = "/transferServlet")
public class TransferServlet extends HttpServlet {

    // 1. 实例化service层对象
//    private TransferService transferService = new TransferServiceImpl();
    //从BeanFactory中获取bean
    private TransferService transferService = (TransferService) BeanFactory.getBean("transferService");

	//省略其他代码
    ....
}

service

public class TransferServiceImpl implements TransferService {

//    private AccountDao accountDao = new JdbcAccountDaoImpl();
    private AccountDao accountDao = (AccountDao) BeanFactory.getBean("accountDao");

	省略其他代码......
}

4.测试代码 插件中运行Tomcat

打开本地的8080 http://localhost:8080 在转账金额处输入数字,点击转出 问题一解决。

思考二:
还有没有更好的办法解决代码耦合: 不在service中向工厂类去获取

解决思路: 1.service中定义private AccountDao accountDao; 并提供setter()

public class TransferServiceImpl implements TransferService {

//    private AccountDao accountDao = new JdbcAccountDaoImpl();
//    private AccountDao accountDao = (AccountDao) BeanFactory.getBean("accountDao");

    private AccountDao accountDao;

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

    //省略部分代码
	......
}

2.在beans.xml中添加properties标签,标签中配置set方法的方法名,以及需要传递的参数类型

<?xml version="1.0" encoding="UTF-8" ?>
<!--根标签beans,里面配置bean子标签,每一个bean子标签都代表一个类的配置-->
<beans>
    <bean id="connectionUtils" class="com.example.utils.ConnectionUtils"></bean>
    <bean id="accountDao" class="com.example.dao.impl.JdbcAccountDaoImpl">
    </bean>
    <bean id="transferService" class="com.example.service.impl.TransferServiceImpl">
		<!--name为setter() 去掉set后的名称  ref指向需要传递的参数类型-->
        <properties name="AccountDao" ref="accountDao"></properties>
    </bean>
</beans>

3.修改xml文件解析 解析所有的properties标签 为属性赋值

public class BeanFactory {

    private static Map<String, Object> map = new HashMap<>();
    /**
     * 解析配置文件  创建所有的bean
     */
    static {
        //读取解析beans.xml
        InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
        SAXReader saxReader = new SAXReader();
        Document document = null;
        try {
            document = saxReader.read(resourceAsStream);
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        //获取根节点  beans
        Element rootElement = document.getRootElement();
        //获取所有的bean标签
        List<Element> beanLst = rootElement.selectNodes("//bean");
        for (Element bean : beanLst) {
            //获取每个标签的id和class
            String id = bean.attributeValue("id");
            String clazz = bean.attributeValue("class");
            //反射创建class实例
            try {
                Class<?> instance = Class.forName(clazz);
                //将实例存储在map中
                map.put(id, instance.newInstance());
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }
        }

        //获取所有的properties标签  为属性赋值
        List<Element> propertiesLst = rootElement.selectNodes("//properties");
        for (Element element : propertiesLst) {
            //获取name和ref
            String name = element.attributeValue("name");
            String ref = element.attributeValue("ref");
            //获取当前元素的父元素   当前properties所在的bean
            Element parentElement = element.getParent();
            //获取id
            String parentId = parentElement.attributeValue("id");
            //获取实例对象
            Object parentObj = map.get(parentId);
            //获取所有方法
            Method[] methods = parentObj.getClass().getMethods();
            for (Method method : methods) {
                //通过方法名匹配到当前这个属性的setter方法
                if (method.getName().equalsIgnoreCase("set" + name)) {
                    try {
                        //执行setter方法   将ref指向的对象赋值给属性
                        method.invoke(parentObj, map.get(ref));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

    }

    /**
     * 提供getBean方法
     */
    public static Object getBean(String id) {
        return map.get(id);
    }

}

4.测试代码

  • 问题二 事务管理

思考:

  1. 需要添加事务管理,手动控制jdbc的事务,同时要确保所有的执行都使用同一个连接connection
  2. 使用同一个connection,需要将连接保存在当前线程中

解决思路:

  1. 创建连接工具类ConnectionUtils
public class ConnectionUtils {

	//线程缓存
    ThreadLocal<Connection> threadLocal = new ThreadLocal<>();

    private static ConnectionUtils connectionUtils = new ConnectionUtils();

	//保证工具类单例
    public static ConnectionUtils getConnectionUtils() {
        return connectionUtils;
    }

	//获取连接
    public Connection getConnection() throws SQLException {
		//通过当前线程获取
        Connection connection = threadLocal.get();
        if (null == connection) {
			//	如果为空,从连接池获取连接,并保存到当前线程
            connection = DruidUtils.getInstance().getConnection();
            threadLocal.set(connection);
        }
        return connection;
    }
}
  1. 修改dao层 必须关闭con.close() 不然连接关闭后将不再是同一个连接
public class JdbcAccountDaoImpl implements AccountDao {

    @Override
    public Account queryAccountByCardNo(String cardNo) throws Exception {
        //从连接池获取连接
//         Connection con = DruidUtils.getInstance().getConnection();
         Connection con = ConnectionUtils.getConnectionUtils().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();
        Connection con = ConnectionUtils.getConnectionUtils().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;
    }
}
  1. 修改service
@Override
    public void transfer(String fromCardNo, String toCardNo, int money) throws Exception {
        try {
            //取消事务自动提交
            ConnectionUtils.getConnectionUtils().getConnection().setAutoCommit(false);
            Account from = accountDao.queryAccountByCardNo(fromCardNo);
            Account to = accountDao.queryAccountByCardNo(toCardNo);

            from.setMoney(from.getMoney()-money);
            to.setMoney(to.getMoney()+money);

            accountDao.updateAccountByCardNo(to);
            //测试异常
            int i = 1/0;
            accountDao.updateAccountByCardNo(from);

            //手动commit
            ConnectionUtils.getConnectionUtils().getConnection().commit();
        } catch (Exception e) {
            e.printStackTrace();
            //有异常回滚事务
            ConnectionUtils.getConnectionUtils().getConnection().rollback();
        }
	}
  1. 进一步改造: 将设置自动提交、提交、回滚封装工具类
/**
 * 事务管理器类:负责手动事务的开启、提交、回滚
 */
public class TransactionManager {
    private static TransactionManager transactionManager = new TransactionManager();

    public static TransactionManager getTransactionManager() {
        return transactionManager;
    }

    public void setAutoCommit() throws SQLException {
        ConnectionUtils.getConnectionUtils().getConnection().setAutoCommit(false);
    }

    public void commit() throws SQLException {
        ConnectionUtils.getConnectionUtils().getConnection().commit();
    }

    public void rollback() throws SQLException {
        ConnectionUtils.getConnectionUtils().getConnection().rollback();
    }

}
@Override
    public void transfer(String fromCardNo, String toCardNo, int money) throws Exception {
        try {
//            //取消事务自动提交
//            ConnectionUtils.getConnectionUtils().getConnection().setAutoCommit(false);
            TransactionManager.getTransactionManager().setAutoCommit();
            Account from = accountDao.queryAccountByCardNo(fromCardNo);
            Account to = accountDao.queryAccountByCardNo(toCardNo);

            from.setMoney(from.getMoney()-money);
            to.setMoney(to.getMoney()+money);

            accountDao.updateAccountByCardNo(to);
            int i = 1/0;
            accountDao.updateAccountByCardNo(from);

            //手动commit
//            ConnectionUtils.getConnectionUtils().getConnection().commit();
            TransactionManager.getTransactionManager().commit();
        } catch (Exception e) {
            e.printStackTrace();
            //有异常回滚事务
//            ConnectionUtils.getConnectionUtils().getConnection().rollback();
            TransactionManager.getTransactionManager().rollback();
        }
    }

5.再进一步改造,将工具类都添加到容器中,通过setter()自动注入 service

public class TransferServiceImpl implements TransferService {

    private AccountDao accountDao;

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

    private TransactionManager transactionManager;

    public void setTransactionManager(TransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    @Override
    public void transfer(String fromCardNo, String toCardNo, int money) throws Exception {


        try {
            //取消事务自动提交
//            ConnectionUtils.getConnectionUtils().getConnection().setAutoCommit(false);
//            TransactionManager.getTransactionManager().setAutoCommit();
            transactionManager.setAutoCommit();
            Account from = accountDao.queryAccountByCardNo(fromCardNo);
            Account to = accountDao.queryAccountByCardNo(toCardNo);

            from.setMoney(from.getMoney()-money);
            to.setMoney(to.getMoney()+money);

            accountDao.updateAccountByCardNo(to);
            int i = 1/0;
            accountDao.updateAccountByCardNo(from);

            //手动commit
//            ConnectionUtils.getConnectionUtils().getConnection().commit();
//            TransactionManager.getTransactionManager().commit();
            transactionManager.commit();
        } catch (Exception e) {
            e.printStackTrace();
            //有异常回滚事务
//            ConnectionUtils.getConnectionUtils().getConnection().rollback();
//            TransactionManager.getTransactionManager().rollback();
            transactionManager.rollback();
        }
    }
}

dao

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.getConnectionUtils().getConnection();
         Connection con = connectionUtils.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;
    }

    ......
}

配置beans.xml 之前将property标签写成properties,这两个标签都可以,名字是自定义的,解析的时候对应上就可以了 只不过spring中使用的是property

<?xml version="1.0" encoding="UTF-8" ?>
<!--根标签beans,里面配置bean子标签,每一个bean子标签都代表一个类的配置-->
<beans>
    <bean id="connectionUtils" class="com.example.utils.ConnectionUtils"></bean>
    <bean id="accountDao" class="com.example.dao.impl.JdbcAccountDaoImpl">
        <property name="ConnectionUtils" ref="connectionUtils"></property>
    </bean>
    <bean id="transferService" class="com.example.service.impl.TransferServiceImpl">
        <property name="AccountDao" ref="accountDao"></property>
        <property name="TransactionManager" ref="transactionManager"></property>
    </bean>
    <bean id="transactionManager" class="com.example.utils.TransactionManager">
        <property name="ConnectionUtils" ref="connectionUtils"></property>
    </bean>
</beans>

修改BeanFactory中的配置文件解析,新增解析properties标签,使用反射调用class中的setter(),为属性赋值

public class BeanFactory {

    private static Map<String, Object> map = new HashMap<>();
    /**
     * 解析配置文件  创建所有的bean
     */
    static {
        //读取解析beans.xml
        InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
        SAXReader saxReader = new SAXReader();
        Document document = null;
        try {
            document = saxReader.read(resourceAsStream);
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        //获取根节点  beans
        Element rootElement = document.getRootElement();
        //获取所有的bean标签
        List<Element> beanLst = rootElement.selectNodes("//bean");
        for (Element bean : beanLst) {
            //获取每个标签的id和class
            String id = bean.attributeValue("id");
            String clazz = bean.attributeValue("class");
            //反射创建class实例
            try {
                Class<?> instance = Class.forName(clazz);
                //将实例存储在map中
                map.put(id, instance.newInstance());
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }
        }

        //获取所有的properties标签  为属性赋值
        List<Element> propertiesLst = rootElement.selectNodes("//properties");
        for (Element element : propertiesLst) {
            //获取name和ref
            String name = element.attributeValue("name");
            String ref = element.attributeValue("ref");
            //获取当前元素的父元素   当前properties所在的bean
            Element parentElement = element.getParent();
            //获取id
            String parentId = parentElement.attributeValue("id");
            //获取实例对象
            Object parentObj = map.get(parentId);
            //获取所有方法
            Method[] methods = parentObj.getClass().getMethods();
            for (Method method : methods) {
                //通过方法名匹配到当前这个属性的setter方法
                if (method.getName().equalsIgnoreCase("set" + name)) {
                    try {
                        //执行setter方法   将ref指向的对象赋值给属性
                        method.invoke(parentObj, map.get(ref));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

    }

    /**
     * 提供getBean方法
     */
    public static Object getBean(String id) {
        return map.get(id);
    }

}

测试代码 ......

6.对方法进行增强,抽取事务管理,不在service中添加,通过代理的方式增强service

a. 新增ProxyFactory,通过类型获取代理类,ProxyFactory中需要注入事务管理类TransactionManager

public class ProxyFactory {

    private TransactionManager transactionManager;

    public void setTransactionManager(TransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    public Object getJdkProxyClass(Object obj) {
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object invoke = null;
                try {
                    //取消事务自动提交
                    transactionManager.setAutoCommit();
                    invoke = method.invoke(obj, args);
                    //手动commit
                    transactionManager.commit();
                } catch (Exception e) {
                    e.printStackTrace();
                    //有异常回滚事务
                    transactionManager.rollback();
                    throw e;
                }

                return invoke;
            }
        });
    }
}

b. 去掉service中的事务管理

public class TransferServiceImpl implements TransferService {

//    private AccountDao accountDao = new JdbcAccountDaoImpl();
//    private AccountDao accountDao = (AccountDao) BeanFactory.getBean("accountDao");

    private AccountDao accountDao;

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


    @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 i = 1/0;
            accountDao.updateAccountByCardNo(from);
    }
}
    <bean id="transferService" class="com.example.service.impl.TransferServiceImpl">
        <property name="AccountDao" ref="accountDao"></property>
<!--        <property name="TransactionManager" ref="transactionManager"></property>-->
    </bean>

c. 修改servlet中的service获取

@WebServlet(name="transferServlet",urlPatterns = "/transferServlet")
public class TransferServlet extends HttpServlet {

    //将proxyFactory注册到beanFactory中
    private ProxyFactory proxyFactory = (ProxyFactory) BeanFactory.getBean("proxyFactory");
    //调用proxyFactory.getJdkProxyClass()获取transferService的代理类
    private TransferService transferService = (TransferService) proxyFactory.getJdkProxyClass(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));
    }
}

d. 测试代码 ......