手擼一個IOC容器

女友在高考發表於2021-11-11

IoC

什麼是IoC?

IoC是Inversion of Control(控制反轉)的簡稱,注意它是一個技術思想。描述的是物件建立、管理的事情。

  • 傳統開發方式:比如類A依賴類B,往往會在類A裡面new一個B的物件。

  • IoC開發方式:我們不用去new物件,由IoC容器幫我們例項化物件並進行管理。我們需要B物件,就問IoC容器要即可。

控制反轉就是說將物件建立、管理的權力交給了外部環境(IoC容器)。

IoC的作用:解決了物件之間的耦合問題。

什麼是DI?

DI是Dependancy Injection(依賴注入)的簡稱,指容器會把物件依賴的其他物件注入。比如A物件裡宣告瞭一個B的屬性,那麼就需要容器把B物件注入給A。

什麼是AOP?

AOP是 Aspect oriented Programming(⾯向切⾯程式設計)的簡稱。

在上面的程式碼中,多個方法都出現了相同的程式碼(可以稱之為橫切邏輯程式碼)。這部分程式碼不僅重複,而且跟業務邏輯沒有關係但是混雜在一起。這時AOP出現了,它提供了橫向抽取機制,將這部分橫切程式碼和業務邏輯程式碼分開。

AOP的作用:在不改變原有業務邏輯的情況下,增強橫切邏輯程式碼,解耦合。

手寫IOC

首先我們看一下在沒有Spring之前,我們是怎麼開發一個web程式的呢?

那麼針對上面的兩個問題,我們如何進行解決呢?

  • 我們除了用new例項化物件外,還可以用反射的技術
  • 另外專案中往往有很多物件需要例項化,那麼可以使用工廠模式來進行優化。

綜上,我們可以用工廠模式+反射技術把物件都例項化好,放在一個map裡面,如果需要某個物件,就可以直接從這個map裡面取。

除此之外,我們還需要一個xml檔案,裡面來定義物件的全類名(反射需要),如果有依賴,還需要定義類與類之間的依賴關係。

<beans>

    <bean id="accountDao" class="com.mmc.ioc.dao.impl.AccountDaoImpl"></bean>
    <bean id="transferService" class="com.mmc.ioc.service.impl.TransferServiceImpl">
        <!--這裡的name預設為set+name就是方法名-->
        <property name="AccountDao" ref="accountDao"></property>
    </bean>
</beans>

核心程式碼:

public class BeanFactory {

    private static Map<String,Object> beanMap=new HashMap<>();


    static {
        InputStream inputStream=BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
        SAXReader saxReader=new SAXReader();
        try {
            Document document = saxReader.read(inputStream);
            Element rootElement = document.getRootElement();
            List<Element> beans = rootElement.selectNodes("//bean");
            for (Element element:beans){
                String id = element.attributeValue("id");
                String clazz = element.attributeValue("class");
                Object instance = Class.forName(clazz).newInstance();
                beanMap.put(id,instance);
            }

            //例項完後填充物件的依賴
            List<Element> propertys = rootElement.selectNodes("//property");
            for (Element element:propertys){
                String name = element.attributeValue("name");
                String ref = element.attributeValue("ref");
                Element parent = element.getParent();
                String parentId = parent.attributeValue("id");
                Object instance = beanMap.get(parentId);
                Object refInstance = beanMap.get(ref);
                Method setMethod = instance.getClass().getDeclaredMethod("set" + name,refInstance.getClass().getInterfaces());
                setMethod.invoke(instance,beanMap.get(ref));
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static Object getBean(String name){
       return beanMap.get(name);
    }
}

那麼接下來我們想要使用物件的時候,就不用new了,而是從beanFactory裡面去拿。

這樣一個簡易的AOP就完成了。

手寫AOP實現

我們解決了上面的問題1,那麼問題2事務控制如何解決呢?

分析:資料庫事務歸根結底是Connection的事務,connection.commit()提交事務,connection.rollback()回滾事務。

  • 我們是想保證service裡的方法裡面執行的眾多資料庫操作要麼都成功,要麼都失敗。
  • 同一個service方法裡面的dao層必須要使用的是同一個connection,也就是說同一個執行緒內要是同一個connection,所以可以使用ThreadLocal實現

改造完成:

  @Override
    public void transfer(String fromCardNo, String toCardNo, int money) throws Exception {
        //關閉自動提交
        connectionUtils.getThreadConn().setAutoCommit(false);

        try {
            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=10/0;
            accountDao.updateAccountByCardNo(from);
            //提交事務
            connectionUtils.getThreadConn().commit();
        } catch (Exception e) {
            //回滾事務            
            connectionUtils.getThreadConn().rollback();
            throw e;
        }
    }

在兩次update語句中間手動加了個異常,可以發現資料庫兩條資料都沒變,說明事務控制成功。

但是如果多個方法都需要加事務控制的話,我們需要給多個方法加上下面這一套重複的程式碼

connectionUtils.getThreadConn().setAutoCommit(false);

try {
  //省略部分程式碼
  // -----
  //提交事務
    connectionUtils.getThreadConn().commit();
} catch (Exception e) {
    //回滾事務
    connectionUtils.getThreadConn().rollback();
    throw e;
}

怎麼解決呢?
我們可以通過代理模式給每個方法代理

程式碼如下:

public class ProxyFactory {

    private ConnectionUtils connectionUtils;

    public void setConnectionUtils(ConnectionUtils connectionUtils) {
        this.connectionUtils = connectionUtils;
    }

    public Object getJdkProxy(Object object){
        return Proxy.newProxyInstance(ProxyFactory.class.getClassLoader(), object.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //關閉自動提交
                connectionUtils.getThreadConn().setAutoCommit(false);
                Object result;
                try {
                    result= method.invoke(object,args);
                    //提交事務
                    connectionUtils.getThreadConn().commit();
                } catch (Exception e) {
                    //回滾事務
                    connectionUtils.getThreadConn().rollback();
                    throw e;
                }
                return result;
            }
        });
    }


}

每個需要加事務的物件,只要呼叫getJdkProxy方法獲取到代理物件,再使用代理物件執行方法,就能實現事務控制了。
使用方法如下:

private ProxyFactory proxyFactory = (ProxyFactory) BeanFactory.getBean("proxyFactory");
private TransferService transferService= (TransferService) proxyFactory.getJdkProxy(BeanFactory.getBean("transferService"));

這樣就相當於把橫切邏輯程式碼提取出來了,如果把這套機制抽出來就是AOP的實現了。

相關文章