美文网首页Java
Spring源码高级笔记之——银行案例手写IOC和AOP

Spring源码高级笔记之——银行案例手写IOC和AOP

作者: Java斗帝之路 | 来源:发表于2020-09-28 20:37 被阅读0次

    手写实现lOC和AOP

    上一部分我们理解了loC和AOP思想,我们先不考虑Spring是如何实现这两个思想的,此处准备了一个『银行转账」的案例,请分析该案例在代码层次有什么问题?分析之后使用我们已有知识解决这些问题(痛点)。其实这个过程我们就是在一步步分析并手写实现loC和AOP。

    第1节银行转账案例界面

    第2节银行转账案例表结构

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

    第4节银行转账案例关键代码

    TransferServlet

    packagecom.lagou.edu.servlet;

    importcom.lagou.edu.service.impl.TransferServiceImpl;

    importcom.lagou.edu.utils.JsonUtils;

    importcom.lagou.edu.pojo.Result;

    importcom.lagou.edu.service.TransferService;

    importjavax.servlet.ServletException;

    importjavax.servlet.annotation.WebServlet;

    importjavax.servlet.http.HttpServlet;

    importjavax.servlet.http.HttpServletRequest;

    importjavax.servlet.http.HttpServletResponse;

    importjava.io.IOException;

    /**

    *@author斗帝

    */

    @WebServlet(name="transferServlet",urlPatterns ="/transferServlet")

    publicclassTransferServletextendsHttpServlet{

    // 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接口及实现

    packagecom.lagou.edu.service;

    /**

    *@author斗帝

    */

    publicinterfaceTransferService{

    voidtransfer(String fromCardNo,String toCardNo,intmoney)throws

    Exception;

    }packagecom.lagou.edu.service.impl;

    importcom.lagou.edu.dao.AccountDao;

    importcom.lagou.edu.dao.impl.JdbcAccountDaoImpl;

    importcom.lagou.edu.pojo.Account;

    importcom.lagou.edu.service.TransferService;

    /**

    *@author斗帝

    */

    publicclassTransferServiceImplimplementsTransferService{

    privateAccountDao accountDao =newJdbcAccountDaoImpl();

    @Override

    publicvoidtransfer(String fromCardNo, String toCardNo,intmoney)

    throwsException{

    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的实现类

    packagecom.lagou.edu.dao;

    importcom.lagou.edu.pojo.Account;

    /**

    *@author斗帝

    */

    publicinterfaceAccountDao{

    AccountqueryAccountByCardNo(String cardNo)throwsException;

    intupdateAccountByCardNo(Account account)throwsException;

    }

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

    packagecom.lagou.edu.dao.impl;

    importcom.lagou.edu.pojo.Account;

    importcom.lagou.edu.dao.AccountDao;

    importcom.lagou.edu.utils.DruidUtils;

    importjava.sql.Connection;

    importjava.sql.PreparedStatement;

    importjava.sql.ResultSet;

    /**

    *@author斗帝

    */

    publicclassJdbcAccountDaoImplimplementsAccountDao{

    @Override

    publicAccountqueryAccountByCardNo(String cardNo)throwsException{

    //从连接池获取连接

    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节 银行转账案例代码问题分析

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

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

    第6节问题解决思路

    针对问题—思考:

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

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

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

    针对问题二思考:

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

    第7节 案例代码改造

    (1)针对问题一的代码改造

    beans.xml

    <?xml version="1.0" encoding="UTF-8" ?>

    class="com.lagou.edu.service.impl.TransferServiceImpl">

    class="com.lagou.edu.dao.impl.JdbcAccountDaoImpl">

    增加 BeanFactory.java

    package com.lagou.edu.factory;

    importorg.dom4j.Document;

    importorg.dom4j.DocumentException;

    importorg.dom4j.Element;

    importorg.dom4j.io.SAXReader;

    importjava.io.InputStream;

    importjava.lang.reflect.InvocationTargetException;

    importjava.lang.reflect.Method;

    importjava.util.HashMap;

    importjava.util.List;

    importjava.util.Map;

    /**

    * @author 应癫

    */

    publicclassBeanFactory {

    /**

    * 工厂类的两个任务

    * 任务一:加载解析xml,读取xml中的bean信息,通过反射技术实例化bean对象,然后放入

    map待用

    * 任务二:提供接口方法根据id从map中获取bean(静态方法)

    */

    privatestaticMap map =newHashMap<>();

    static{

    InputStream resourceAsStream =BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");

    SAXReader saxReader =newSAXReader();

    try{

    Documentdocument= saxReader.read(resourceAsStream);

    Element rootElement =document.getRootElement();

    List 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

    修改 TransferServiceImpl

    (2)针对问题二的改造

    增加 ConnectionUtils

    packagecom.lagou.edu.utils;

    importjava.sql.Connection;

    importjava.sql.SQLException;

    /**

    *@author斗帝

    */

    publicclassConnectionUtils{

    /*private ConnectionUtils() {

    }

    private static ConnectionUtils connectionUtils = new

    ConnectionUtils();

    public static ConnectionUtils getInstance() {

    return connectionUtils;

    }*/

    privateThreadLocal threadLocal =newThreadLocal<>();//

    存储当前线程的连接

    /**

    * 从当前线程获取连接

    */

    public Connection getCurrentThreadConn() throws SQLException {

    /**

    * 判断当前线程中是否已经绑定连接,如果没有绑定,需要从连接池获取一个连接绑定到

    当前线程

    */

    Connection connection = threadLocal.get();

    if(connection == null) {

    // 从连接池拿连接并绑定到线程

    connection = DruidUtils.getInstance().getConnection();

    // 绑定到当前线程

    threadLocal.set(connection);

    }

    return connection;

    }

    }

    增加 TransactionManager 事务管理器类

    packagecom.lagou.edu.utils;

    importjava.sql.SQLException;

    /**

    *@author斗帝

    */

    publicclassTransactionManager{

    privateConnectionUtils connectionUtils;

    publicvoidsetConnectionUtils(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 代理工厂类

    packagecom.lagou.edu.factory;

    importcom.lagou.edu.utils.TransactionManager;

    importjava.lang.reflect.InvocationHandler;

    importjava.lang.reflect.Method;

    importjava.lang.reflect.Proxy;

    /**

    *@author斗帝

    */

    publicclassProxyFactory{

    privateTransactionManager transactionManager;

    publicvoidsetTransactionManager(TransactionManager

    transactionManager){

    this.transactionManager = transactionManager;

    }publicObjectgetProxy(Object target){

    returnProxy.newProxyInstance(this.getClass().getClassLoader(),

    target.getClass().getInterfaces(),newInvocationHandler() {

    @Override

    publicObjectinvoke(Object proxy, Method method, Object[]

    args)throwsThrowable{

    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子标签都代表一个类的配置--

    >

    <!--id标识对象,class是类的全限定类名-->

    class="com.lagou.edu.dao.impl.JdbcAccountDaoImpl">

    class="com.lagou.edu.service.impl.TransferServiceImpl">

    <!--set+ name 之后锁定到传值的set方法了,通过反射技术可以调用该方法传入对应

    的值-->

    <!--配置新增的三个Bean-->

    class="com.lagou.edu.utils.ConnectionUtils">

    <!--事务管理器-->

    class="com.lagou.edu.utils.TransactionManager">

    <!--代理对象工厂-->

    修改 JdbcAccountDaoImpl

    packagecom.lagou.edu.dao.impl;

    importcom.lagou.edu.pojo.Account;

    importcom.lagou.edu.dao.AccountDao;

    importcom.lagou.edu.utils.ConnectionUtils;

    importcom.lagou.edu.utils.DruidUtils;

    importjava.sql.Connection;

    importjava.sql.PreparedStatement;

    importjava.sql.ResultSet;

    /**

    *@author斗帝

    */

    publicclassJdbcAccountDaoImplimplementsAccountDao{

    privateConnectionUtils connectionUtils;

    publicvoidsetConnectionUtils(ConnectionUtils connectionUtils){

    this.connectionUtils = connectionUtils;

    }@Override

    publicAccountqueryAccountByCardNo(String cardNo)throwsException{

    //从连接池获取连接

    // 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

    packagecom.lagou.edu.servlet;

    importcom.lagou.edu.factory.BeanFactory;

    importcom.lagou.edu.factory.ProxyFactory;

    importcom.lagou.edu.service.impl.TransferServiceImpl;

    importcom.lagou.edu.utils.JsonUtils;

    importcom.lagou.edu.pojo.Result;

    importcom.lagou.edu.service.TransferService;

    importjavax.servlet.ServletException;

    importjavax.servlet.annotation.WebServlet;

    importjavax.servlet.http.HttpServlet;

    importjavax.servlet.http.HttpServletRequest;

    importjavax.servlet.http.HttpServletResponse;

    importjava.io.IOException;

    /**

    *@author斗帝

    */

    @WebServlet(name="transferServlet",urlPatterns ="/transferServlet")

    publicclassTransferServletextendsHttpServlet{

    // 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));

    }

    }

    好了,银行的案例今天就写到这里,喜欢的朋友可以关注一下小编,此系列文章持续更新中;

    相关文章

      网友评论

        本文标题:Spring源码高级笔记之——银行案例手写IOC和AOP

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