初學原始碼之——銀行案例手寫IOC和AOP

Java鬥帝之路發表於2020-09-28

手寫實現lOC和AOP

上一部分我們理解了loC和AOP思想,我們先不考慮Spring是如何實現這兩個思想的,此處準備了一個『銀行轉賬」的案例,請分析該案例在程式碼層次有什麼問題?分析之後使用我們已有知識解決這些問題(痛點)。其實這個過程我們就是在一步步分析並手寫實現loC和AOP。

第1節銀行轉賬案例介面

Spring原始碼高階筆記之——銀行案例手寫IOC和AOP

 

第2節銀行轉賬案例表結構

Spring原始碼高階筆記之——銀行案例手寫IOC和AOP

 

第3節銀行轉賬案例程式碼呼叫關係

Spring原始碼高階筆記之——銀行案例手寫IOC和AOP

 

第4節銀行轉賬案例關鍵程式碼

  • TransferServlet
package com.lagou.edu.servlet;
import com.lagou.edu.service.impl.TransferServiceImpl;
import com.lagou.edu.utils.JsonUtils;
import com.lagou.edu.pojo.Result;
import com.lagou.edu.service.TransferService;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author 鬥帝
*/
@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介面及實現類
package com.lagou.edu.service;
/**
* @author 鬥帝
*/
public interface TransferService {
void transfer(String fromCardNo,String toCardNo,int money) throws
Exception;
}package com.lagou.edu.service.impl;
import com.lagou.edu.dao.AccountDao;
import com.lagou.edu.dao.impl.JdbcAccountDaoImpl;
import com.lagou.edu.pojo.Account;
import com.lagou.edu.service.TransferService;
/**
* @author 鬥帝
*/
public class TransferServiceImpl implements TransferService {
private AccountDao accountDao = new JdbcAccountDaoImpl();
@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(from);accountDao.updateAccountByCardNo(to); }
  • AccountDao層介面及基於Jdbc的實現類
package com.lagou.edu.dao;
import com.lagou.edu.pojo.Account;
/**
* @author 鬥帝
*/
public interface AccountDao {
Account queryAccountByCardNo(String cardNo) throws Exception;
int updateAccountByCardNo(Account account) throws Exception;
}
  • JdbcAccountDaoImpl(Jdbc技術實現Dao層介面)
package com.lagou.edu.dao.impl;
import com.lagou.edu.pojo.Account;
import com.lagou.edu.dao.AccountDao;
import com.lagou.edu.utils.DruidUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
/**
* @author 鬥帝
*/
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;
}
}

第5節 銀行轉賬案例程式碼問題分析

 

Spring原始碼高階筆記之——銀行案例手寫IOC和AOP

 

(1)問題一:在上述案例實現中,service 層實現類在使用 dao 層物件時,直接在TransferServiceImpl 中通過 AccountDao accountDao = new JdbcAccountDaoImpl() 獲得了 dao層物件,然而一個 new 關鍵字卻將 TransferServiceImpl 和 dao 層具體的一個實現類JdbcAccountDaoImpl 耦合在了一起,如果說技術架構發生一些變動,dao 層的實現要使用其它技術,比如 Mybatis,思考切換起來的成本?每一個 new 的地方都需要修改原始碼,重新編譯,面向介面開發的意義將大打折扣?

(2)問題二:service 層程式碼沒有竟然還沒有進行事務控制 ?!如果轉賬過程中出現異常,將可能導致資料庫資料錯亂,後果可能會很嚴重,尤其在金融業務。

第6節問題解決思路

  • 針對問題—思考:

例項化物件的方式除了new之外,還有什麼技術?反射(需要把類的全限定類名配置在xml中)

  • 考慮使用設計模式中的工廠模式解耦合,另外專案中往往有很多物件需要例項化,那就在工廠中使用反射技術例項化物件,工廠模式很合適
Spring原始碼高階筆記之——銀行案例手寫IOC和AOP

 

  • 更進一步,程式碼中能否只宣告所需例項的介面型別,不出現 new 也不出現工廠類的字眼,如下圖? 能!宣告一個變數並提供 set 方法,在反射的時候將所需要的物件注入進去吧

 

Spring原始碼高階筆記之——銀行案例手寫IOC和AOP

 

  • 針對問題二思考:

service 層沒有新增事務控制,怎麼辦?沒有事務就新增上事務控制,手動控制 JDBC 的Connection 事務,但要注意將Connection和當前執行緒繫結(即保證一個執行緒只有一個Connection,這樣操作才針對的是同⼀個 Connection,進而控制的是同一個事務)

Spring原始碼高階筆記之——銀行案例手寫IOC和AOP

 

第7節 案例程式碼改造

(1)針對問題一的程式碼改造

  • beans.xml

 

<?xml version="1.0" encoding="UTF-8" ?>
<beans>
<bean id="transferService"
class="com.lagou.edu.service.impl.TransferServiceImpl">
<property name="AccountDao" ref="accountDao"></property>
</bean>
<bean id="accountDao"
class="com.lagou.edu.dao.impl.JdbcAccountDaoImpl">
</bean>
</beans>
  • 增加 BeanFactory.java

 

package com.lagou.edu.factory;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author 應癲
*/
public class BeanFactory {
/**
* 工廠類的兩個任務
* 任務一:載入解析xml,讀取xml中的bean資訊,通過反射技術例項化bean物件,然後放入
map待用
* 任務二:提供介面方法根據id從map中獲取bean(靜態方法)
*/
private static Map<String,Object> map = new HashMap<>();
static {
InputStream resourceAsStream =BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
SAXReader saxReader = new SAXReader();
try {
Document document = saxReader.read(resourceAsStream);
Element rootElement = document.getRootElement();
List<Element> 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
Spring原始碼高階筆記之——銀行案例手寫IOC和AOP

 

  • 修改 TransferServiceImpl
Spring原始碼高階筆記之——銀行案例手寫IOC和AOP

 

(2)針對問題二的改造

 

  • 增加 ConnectionUtils
package com.lagou.edu.utils;
import java.sql.Connection;
import java.sql.SQLException;
/**
* @author 鬥帝
*/
public class ConnectionUtils {
/*private ConnectionUtils() {
}
private static ConnectionUtils connectionUtils = new
ConnectionUtils();
public static ConnectionUtils getInstance() {
return connectionUtils;
}*/
private ThreadLocal<Connection> threadLocal = new ThreadLocal<>(); //
儲存當前執行緒的連線
/**
* 從當前執行緒獲取連線
*/
public Connection getCurrentThreadConn() throws SQLException {
/**
* 判斷當前執行緒中是否已經繫結連線,如果沒有繫結,需要從連線池獲取一個連線繫結到
當前執行緒
*/
Connection connection = threadLocal.get();
if(connection == null) {
// 從連線池拿連線並繫結到執行緒
connection = DruidUtils.getInstance().getConnection();
// 繫結到當前執行緒
threadLocal.set(connection);
}
return connection;
}
}
  • 增加 TransactionManager 事務管理器類
package com.lagou.edu.utils;
import java.sql.SQLException;
/**
* @author 鬥帝
*/
public class TransactionManager {
private ConnectionUtils connectionUtils;
public void setConnectionUtils(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 代理工廠類

 

package com.lagou.edu.factory;
import com.lagou.edu.utils.TransactionManager;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @author 鬥帝
*/
public class ProxyFactory {
private TransactionManager transactionManager;
public void setTransactionManager(TransactionManager
transactionManager) {
this.transactionManager = transactionManager;
} public Object getProxy(Object target) {
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[]
args) throws Throwable {
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子標籤都代表一個類的配置--
><beans>
<!--id標識物件,class是類的全限定類名-->
<bean id="accountDao"
class="com.lagou.edu.dao.impl.JdbcAccountDaoImpl">
<property name="ConnectionUtils" ref="connectionUtils"/>
</bean>
<bean id="transferService"
class="com.lagou.edu.service.impl.TransferServiceImpl">
<!--set+ name 之後鎖定到傳值的set方法了,通過反射技術可以呼叫該方法傳入對應
的值-->
<property name="AccountDao" ref="accountDao"></property>
</bean>
<!--配置新增的三個Bean-->
<bean id="connectionUtils"
class="com.lagou.edu.utils.ConnectionUtils"></bean>
<!--事務管理器-->
<bean id="transactionManager"
class="com.lagou.edu.utils.TransactionManager">
<property name="ConnectionUtils" ref="connectionUtils"/>
</bean>
<!--代理物件工廠-->
<bean id="proxyFactory" class="com.lagou.edu.factory.ProxyFactory">
<property name="TransactionManager" ref="transactionManager"/>
</bean>
</beans>
  • 修改 JdbcAccountDaoImpl
package com.lagou.edu.dao.impl;
import com.lagou.edu.pojo.Account;
import com.lagou.edu.dao.AccountDao;
import com.lagou.edu.utils.ConnectionUtils;
import com.lagou.edu.utils.DruidUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
/**
* @author 鬥帝
*/
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.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
package com.lagou.edu.servlet;
import com.lagou.edu.factory.BeanFactory;
import com.lagou.edu.factory.ProxyFactory;
import com.lagou.edu.service.impl.TransferServiceImpl;
import com.lagou.edu.utils.JsonUtils;
import com.lagou.edu.pojo.Result;
import com.lagou.edu.service.TransferService;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author 鬥帝
*/
@WebServlet(name="transferServlet",urlPatterns = "/transferServlet")
public class TransferServlet extends HttpServlet {
// 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));
}
}

好了,銀行的案例今天就寫到這裡,喜歡的朋友可以關注一下小編,此係列文章持續更新中;

看完三件事❤️

如果你覺得這篇內容對你還蠻有幫助,我想邀請你幫我三個小忙:

  1. 點贊,轉發,有你們的 『點贊和評論』,才是我創造的動力。

  2. 關注公眾號 『 Java鬥帝 』,不定期分享原創知識。

  3. 同時可以期待後續文章ing?

 

相關文章