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的實現了。