一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

通過一個銀行轉(zhuǎn)賬的案例,手寫實(shí)現(xiàn)IOC 和 AOP

 啟云_9137 2020-08-18

通過上一篇面試被問了幾百遍的 IoC 和 AOP,還在傻傻搞不清楚?我們了解了 IOC 和 AOP 這兩個思想,下面我們先不去考慮Spring是如何實(shí)現(xiàn)這兩個思想的,先通過一個銀行轉(zhuǎn)賬的案例,分析一下該案例在代碼層面存在什么問題?分析之后使用我們已有的知識來解決這些問題(痛點(diǎn))。

其實(shí)這個過程就是在一步步分析并手動實(shí)現(xiàn) IOC 和 AOP 。

案例介紹

銀行轉(zhuǎn)賬:賬戶A向賬戶B轉(zhuǎn)賬(賬戶A減錢,賬戶B加錢)。為了簡單起見,在前端頁面中寫死了兩個賬戶。每次只需要輸入轉(zhuǎn)賬金額,進(jìn)行轉(zhuǎn)賬操作,驗(yàn)證功能即可。

案例表結(jié)構(gòu)

name varcher 255 用戶名money int 255 賬戶金額cardNo varcher 255 銀行卡號

案例代碼調(diào)用關(guān)系

通過一個銀行轉(zhuǎn)賬的案例,手寫實(shí)現(xiàn)IOC 和 AOP

核心代碼

TransferServlet

@WebServlet(name='transferServlet',urlPatterns = '/transferServlet')public class TransferServlet extends HttpServlet {    // 1. 實(shí)例化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 {        // 設(shè)置請求體的字符編碼        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. 調(diào)用service層方法            transferService.transfer(fromCardNo,toCardNo,money);            result.setStatus('200');        } catch (Exception e) {            e.printStackTrace();            result.setStatus('201');            result.setMessage(e.toString());        }        // 響應(yīng)        resp.setContentType('application/json;charset=utf-8');        resp.getWriter().print(JsonUtils.object2Json(result));    }}

TransferService

通過一個銀行轉(zhuǎn)賬的案例,手寫實(shí)現(xiàn)IOC 和 AOP

TransferServiceImpl

通過一個銀行轉(zhuǎn)賬的案例,手寫實(shí)現(xiàn)IOC 和 AOP

AccountDao

通過一個銀行轉(zhuǎn)賬的案例,手寫實(shí)現(xiàn)IOC 和 AOP

JdbcAccountDaoImpl

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; }}

案例問題分析

通過一個銀行轉(zhuǎn)賬的案例,手寫實(shí)現(xiàn)IOC 和 AOP

通過上面的流程分析以及簡要代碼,我們可以發(fā)現(xiàn)如下問題:

問題一: new 關(guān)鍵字將 service 層的實(shí)現(xiàn)類 TransferServiceImpl 和 Dao 層的具體實(shí)現(xiàn)類 JdbcAccountDaoImpl 耦合在了一起,當(dāng)需要切換Dao層實(shí)現(xiàn)類的時候必須要修改 service 的代碼、重新編譯,這樣不符合面向接口開發(fā)的最優(yōu)原則。

問題二: service 層沒有事務(wù)控制,如果轉(zhuǎn)賬過程中出現(xiàn)異常可能會導(dǎo)致數(shù)據(jù)錯亂,后果很嚴(yán)重,尤其是在金融銀行領(lǐng)域。

問題解決思路

new關(guān)鍵字耦合問題解決方案

實(shí)例化對象的方式處理new之外,還有什么技術(shù)?

答:反射(將類的權(quán)限定類名配置在xml文件中)

項(xiàng)目中往往有很多對象需要實(shí)例化,考慮使用工程模式通過反射來實(shí)例化對象。(工廠模式是解耦合非常好的一種方式)

代碼中能否只聲明所需實(shí)例的接口類型,不出現(xiàn)new關(guān)鍵字,也不出現(xiàn)工廠類的字眼?

答:可以,聲明一個變量并提供一個set方法,在反射的時候?qū)⑺枰膶ο笞⑷脒M(jìn)去。

通過一個銀行轉(zhuǎn)賬的案例,手寫實(shí)現(xiàn)IOC 和 AOP
通過一個銀行轉(zhuǎn)賬的案例,手寫實(shí)現(xiàn)IOC 和 AOP

new關(guān)鍵字耦合問題代碼改造

首先定義 bean.xml 文件

通過一個銀行轉(zhuǎn)賬的案例,手寫實(shí)現(xiàn)IOC 和 AOP

定義BeanFactory

BeanFactory

/** * 工廠類,生產(chǎn)對象(使用反射技術(shù)) * 任務(wù)一:讀取解析xml,通過反射技術(shù)實(shí)例化對象并且存儲待用(map集合) * 任務(wù)二:對外提供獲取實(shí)例對象的接口(根據(jù)id獲取) */public class BeanFactory {    private static Map<String,Object> map = new HashMap<>();  // 存儲對象    /**     * 讀取解析xml,通過反射技術(shù)實(shí)例化對象并且存儲待用(map集合)     */    static {        // 加載xml        InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream('beans.xml');        // 解析xml        SAXReader saxReader = new SAXReader();        try {            Document document = saxReader.read(resourceAsStream);            // 獲取根元素            Element rootElement = document.getRootElement();            List<Element> beanList = rootElement.selectNodes('//bean');            for (int i = 0; i < beanList.size(); i++) {                Element element =  beanList.get(i);                // 處理每個bean元素,獲取到該元素的id 和 class 屬性                String id = element.attributeValue('id');        // accountDao                String clazz = element.attributeValue('class');  // com.yanliang.dao.impl.JdbcAccountDaoImpl                // 通過反射技術(shù)實(shí)例化對象                Class<?> aClass = Class.forName(clazz);                Object o = aClass.newInstance();  // 實(shí)例化之后的對象                // 存儲到map中待用                map.put(id,o);            }            // 實(shí)例化完成之后維護(hù)對象的依賴關(guān)系,檢查哪些對象需要傳值進(jìn)入,根據(jù)它的配置,我們傳入相應(yīng)的值            // 有property子元素的bean就有傳值需求            List<Element> propertyList = rootElement.selectNodes('//property');            // 解析property,獲取父元素            for (int i = 0; i < propertyList.size(); i++) {                Element element =  propertyList.get(i);   //<property name='AccountDao' ref='accountDao'></property>                String name = element.attributeValue('name');                String ref = element.attributeValue('ref');                // 找到當(dāng)前需要被處理依賴關(guān)系的bean                Element parent = element.getParent();                // 調(diào)用父元素對象的反射功能                String parentId = parent.attributeValue('id');                Object parentObject = map.get(parentId);                // 遍歷父對象中的所有方法,找到'set' + name                Method[] methods = parentObject.getClass().getMethods();                for (int j = 0; j < methods.length; j++) {                    Method method = methods[j];                    if(method.getName().equalsIgnoreCase('set' + name)) {  // 該方法就是 setAccountDao(AccountDao accountDao)                        method.invoke(parentObject,map.get(ref));                    }                }                // 把處理之后的parentObject重新放到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();        }    }    /**     * 對外提供獲取實(shí)例對象的接口(根據(jù)id獲?。?    * @param id     * @return     */    public static  Object getBean(String id) {        return map.get(id);    }}

對象的實(shí)例化工作交給BeanFactory來進(jìn)行之后,我們再具體使用是就可以像如下這樣了:

通過一個銀行轉(zhuǎn)賬的案例,手寫實(shí)現(xiàn)IOC 和 AOP

事務(wù)控制問題分析

在轉(zhuǎn)賬的業(yè)務(wù)代碼中手動模擬轉(zhuǎn)賬異常,來驗(yàn)證一下。在兩個賬戶的轉(zhuǎn)入和轉(zhuǎn)出之間模擬一個分母為0的異常。

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

然后啟動程序,點(diǎn)擊轉(zhuǎn)賬(李大雷 向 韓梅梅轉(zhuǎn) 100 ¥)之后,會出現(xiàn)如下錯誤。

通過一個銀行轉(zhuǎn)賬的案例,手寫實(shí)現(xiàn)IOC 和 AOP

這時我們再查看數(shù)據(jù)庫

通過一個銀行轉(zhuǎn)賬的案例,手寫實(shí)現(xiàn)IOC 和 AOP

發(fā)現(xiàn) 韓梅梅 的賬戶增加了100¥,但是李大雷的賬戶并沒有減少(兩個賬戶原本都有10000¥)。

出現(xiàn)這個問題的原因就是因?yàn)镾ervice層沒有事務(wù)控制的功能,在轉(zhuǎn)賬過程中出現(xiàn)錯誤(轉(zhuǎn)入和轉(zhuǎn)出之間出現(xiàn)異常,轉(zhuǎn)入已經(jīng)完成,轉(zhuǎn)出沒有進(jìn)行)這事就會造成上面的問題。

數(shù)據(jù)庫的事務(wù)問題歸根結(jié)底是 Connection 的事務(wù)

  • connection.commit() 提交事務(wù)
  • connection.rollback() 回滾事務(wù)

在上面銀行轉(zhuǎn)賬的案例中,兩次update操作使用的是兩個數(shù)據(jù)庫連接,這樣的話,肯定就不屬于同一個事務(wù)控制了。

解決思路:

通過上面的分析,我們得出問題的原因是兩次update使用了兩個不同的connection連接。那么要想解決這個問題,我們就需要讓兩次update使用同一個connection連接

兩次update屬于同一個線程內(nèi)的執(zhí)行調(diào)用,我們可以給當(dāng)前線程綁定一個Connection,和當(dāng)前線程有關(guān)系的數(shù)據(jù)庫操作都去使用這個connection(從當(dāng)前線程中獲取,第一次使用連接,發(fā)現(xiàn)當(dāng)前線程沒有,就從連接池獲取一個連接綁定到當(dāng)前線程)

另一方面,目前事務(wù)控制是在Dao層進(jìn)行的(connection),我們需要將事務(wù)控制提到service層(service層才是具體執(zhí)行業(yè)務(wù)邏輯的地方,這里可能會調(diào)用多個dao層的方法,我們需要對service層的方法進(jìn)行整體的事務(wù)控制)。

有了上面兩個思路,下面我們進(jìn)行代碼修改。

事務(wù)控制代碼修改

增加 ConnectionUtils 工具類

ConnectionUtils

通過一個銀行轉(zhuǎn)賬的案例,手寫實(shí)現(xiàn)IOC 和 AOP

增加 TransactionManager 事務(wù)管理類

TransactionManager

通過一個銀行轉(zhuǎn)賬的案例,手寫實(shí)現(xiàn)IOC 和 AOP

增加代理工廠 ProxyFactory

ProxyFactory

/** * 代理對象工廠:生成代理對象的 */public class ProxyFactory {    private TransactionManager transactionManager;    public void setTransactionManager(TransactionManager transactionManager) {        this.transactionManager = transactionManager;    }    /**     * Jdk動態(tài)代理     * @param obj  委托對象     * @return   代理對象     */    public Object getJdkProxy(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 result = null;                        try{                            // 開啟事務(wù)(關(guān)閉事務(wù)的自動提交)                            transactionManager.beginTransaction();                            result = method.invoke(obj,args);                            // 提交事務(wù)                            transactionManager.commit();                        }catch (Exception e) {                            e.printStackTrace();                            // 回滾事務(wù)                            transactionManager.rollback();                            // 拋出異常便于上層servlet捕獲                            throw e;                        }                        return result;                    }                });    }    /**     * 使用cglib動態(tài)代理生成代理對象     * @param obj 委托對象     * @return     */    public Object getCglibProxy(Object obj) {        return  Enhancer.create(obj.getClass(), new MethodInterceptor() {            @Override            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {                Object result = null;                try{                    // 開啟事務(wù)(關(guān)閉事務(wù)的自動提交)                    transactionManager.beginTransaction();                    result = method.invoke(obj,objects);                    // 提交事務(wù)                    transactionManager.commit();                }catch (Exception e) {                    e.printStackTrace();                    // 回滾事務(wù)                    transactionManager.rollback();                    // 拋出異常便于上層servlet捕獲                    throw e;                }                return result;            }        });    }}

修改beans.xml文件

beans

通過一個銀行轉(zhuǎn)賬的案例,手寫實(shí)現(xiàn)IOC 和 AOP

修改 JdbcAccountDaoImpl的實(shí)現(xiàn)

JdbcAccountDaoImpl

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(); // 改造為:從當(dāng)前線程當(dāng)中獲取綁定的connection連接 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 con = DruidUtils.getInstance().getConnection(); // 改造為:從當(dāng)前線程當(dāng)中獲取綁定的connection連接 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

TransferServlet

@WebServlet(name='transferServlet',urlPatterns = '/transferServlet')public class TransferServlet extends HttpServlet {//    // 1. 實(shí)例化service層對象//    private TransferService transferService = new TransferServiceImpl();    // 改造為通過Bean工程獲取service層對象//    private TransferService transferService = (TransferService) BeanFactory.getBean('transferService');    // 從工程獲取委托對象(委托對象增強(qiáng)了事務(wù)控制的功能)    private ProxyFactory proxyFactory = (ProxyFactory) BeanFactory.getBean('proxyFactory');    private TransferService transferService = (TransferService) proxyFactory.getProxy(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 {        // 設(shè)置請求體的字符編碼        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. 調(diào)用service層方法            transferService.transfer(fromCardNo,toCardNo,money);            result.setStatus('200');        } catch (Exception e) {            e.printStackTrace();            result.setStatus('201');            result.setMessage(e.toString());        }        // 響應(yīng)        resp.setContentType('application/json;charset=utf-8');        resp.getWriter().print(JsonUtils.object2Json(result));    }}

改造完之后,我們再次進(jìn)行測試,這時會發(fā)現(xiàn)當(dāng)轉(zhuǎn)賬過程中出現(xiàn)錯誤時,事務(wù)能夠成功的被控制住(轉(zhuǎn)出賬戶不會少錢,轉(zhuǎn)入賬戶不會多錢)。

為什么要使用代理的方式來實(shí)現(xiàn)事務(wù)控制?

這里我們可以考慮一個問題,為什么要使用代理的方式來實(shí)現(xiàn)事務(wù)控制?

如果沒有使用代理的方式,我們要向?qū)崿F(xiàn)事務(wù)控制這需要將,事務(wù)控制的相關(guān)代碼寫在service層的TransferServiceImpl 具體實(shí)現(xiàn)中。

public class TransferServiceImpl implements TransferService { // 最佳狀態(tài) private AccountDao accountDao; // 構(gòu)造函數(shù)傳值/set方法傳值 public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } @Override public void transfer(String fromCardNo, String toCardNo, int money) throws Exception { try{ // 開啟事務(wù)(關(guān)閉事務(wù)的自動提交) TransactionManager.getInstance().beginTransaction();*/ 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); // 提交事務(wù) TransactionManager.getInstance().commit(); }catch (Exception e) { e.printStackTrace(); // 回滾事務(wù) TransactionManager.getInstance().rollback(); // 拋出異常便于上層servlet捕獲 throw e; } }}

這樣的話,事務(wù)控制和具體的業(yè)務(wù)代碼就耦合在了一起,如果有多個方法都需要實(shí)現(xiàn)事務(wù)控制的功能,我們需要在每個業(yè)務(wù)方法是都添加上這些代碼。這樣將會出現(xiàn)大量的重復(fù)代碼。所以這里使用了 AOP 的思想通過動態(tài)代理的方式實(shí)現(xiàn)了事務(wù)控制。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多

    国产熟女一区二区三区四区| 亚洲午夜精品视频观看| 亚洲精品国产主播一区| 色丁香一区二区黑人巨大| 精品欧美国产一二三区| 热久久这里只有精品视频| 久久热这里只有精品视频| 亚洲综合色婷婷七月丁香| 日韩蜜桃一区二区三区| 国产又粗又长又大高潮视频| 日韩欧美91在线视频| 日韩女优精品一区二区三区| 婷婷激情五月天丁香社区| 日韩免费国产91在线| 欧美日韩校园春色激情偷拍| 伊人色综合久久伊人婷婷| 一级欧美一级欧美在线播| 日本少妇三级三级三级| 青青操在线视频精品视频| 狠狠做五月深爱婷婷综合| 国产成人午夜av一区二区| 中文字幕日韩欧美一区| 久久国产成人精品国产成人亚洲| 久久精品国产熟女精品| 狠色婷婷久久一区二区三区| 国产在线观看不卡一区二区| 少妇被粗大进猛进出处故事| 久久本道综合色狠狠五月| 亚洲精品小视频在线观看| 欧美午夜伦理在线观看| av免费视屏在线观看| 精品国自产拍天天青青草原| 国产又大又硬又粗又湿| 麻豆亚州无矿码专区视频| 国产亚洲精品岁国产微拍精品| 欧美性高清一区二区三区视频| 福利视频一区二区三区| 欧美国产日本免费不卡| 高清不卡视频在线观看| 韩国激情野战视频在线播放| 人妻久久这里只有精品|