I was asked hundreds of times about IoC and AOP in my last interview, and I was still silly and confused?We understand the two ideas of IOC and AOP. Next, let's not consider how Spring achieves these two ideas. First, through a case of bank transfer, we analyze the code level problems in this case.Use our existing knowledge to solve these problems (pain points) after analysis.
In fact, this process is to analyze and manually implement IOC and AOP step by step.
Case introduction
Bank transfer: Account A transfers to Account B (Account A reduces money, Account B increases money).For simplicity, two accounts were written on the front page.Each time you just need to enter the amount of transfer, carry out the transfer operation, and verify the function.
Case table structure
Name) varcher 255 user name money * int * 255 account amount cardNo. varcher 255 bank card number
Case Code Call Relationships
Core Code
TransferServlet
@WebServlet(name="transferServlet",urlPatterns = "/transferServlet") public class TransferServlet extends HttpServlet { //1. Instantiate the service layer object 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 { //Set the character encoding of the requester 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. Call service layer methods transferService.transfer(fromCardNo,toCardNo,money); result.setStatus("200"); } catch (Exception e) { e.printStackTrace(); result.setStatus("201"); result.setMessage(e.toString()); } //Response resp.setContentType("application/json;charset=utf-8"); resp.getWriter().print(JsonUtils.object2Json(result)); } }
TransferService
TransferServiceImpl
AccountDao
JdbcAccountDaoImpl
public class JdbcAccountDaoImpl implements AccountDao { @Override public Account queryAccountByCardNo(String cardNo) throws Exception { //Get a connection from the connection pool 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 { //Get a connection from the connection pool 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; } }
Case Problem Analysis
From the above process analysis and brief code, we can find the following problems:
Question 1: The new keyword couples the service layer's implementation class TransferServiceImpl with the Dao layer's implementation class JdbcAccountDaoImpl. When you need to switch the Dao layer's implementation class, you must modify the service's code and recompile it, which does not conform to the best principles for Interface-oriented development.
Problem 2: There is no transaction control at the service level. If there is an exception in the transfer process, it may lead to data confusion with serious consequences, especially in the financial banking sector.
Problem-solving ideas
new Keyword Coupling Problem Solution
What are the techniques besides instantiating objects to handle new?
Answer: Reflection (configure permission-specific class names for classes in xml files)
There are often many objects in a project that need to be instantiated. Consider using project mode to instantiate objects through reflection.(Factory mode is a very good way to decouple)
Can you just state the interface type of the required instance in your code, without the new keyword or the word factory class?
A: Yes, you can declare a variable and provide a set method to inject the desired object into it when reflected.
Code modification of new keyword coupling problem
Define firstBean.xmlfile
Define BeanFactory
BeanFactory
/** * Factory class, production object (using reflection technology) * Task 1: Read parse xml, instantiate objects through reflection technology, and store standby (map collection) * Task 2: Provide an interface for acquiring instance objects (based on id) */ public class BeanFactory { private static Map<String,Object> map = new HashMap<>(); //Storage object /** * Read parse xml, instantiate objects through reflection technology, and store standby (map collection) */ static { //Load xml InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml"); //Parse xml SAXReader saxReader = new SAXReader(); try { Document document = saxReader.read(resourceAsStream); //Get the root element Element rootElement = document.getRootElement(); List<Element> beanList = rootElement.selectNodes("//bean"); for (int i = 0; i < beanList.size(); i++) { Element element = beanList.get(i); //Process each bean element to get its id and class attributes String id = element.attributeValue("id"); // accountDao String clazz = element.attributeValue("class"); // com.yanliang.dao.impl.JdbcAccountDaoImpl //Instantiate objects using reflection technology Class<?> aClass = Class.forName(clazz); Object o = aClass.newInstance(); //Object after instantiation //Store in map for use map.put(id,o); } //Maintain object dependencies after instantiation is complete, check which objects need to pass in values, and depending on its configuration, we pass in values //Beans with property child elements have value passing requirements List<Element> propertyList = rootElement.selectNodes("//property"); //Parse property to get parent element 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"); //Find the bean that currently needs to be processed Element parent = element.getParent(); //Call reflection on parent element objects String parentId = parent.attributeValue("id"); Object parentObject = map.get(parentId); //Go through all methods in the parent object and find "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)) { //The method is setAccountDao(AccountDao accountDao) method.invoke(parentObject,map.get(ref)); } } //Re-place the processed parentObject into the 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(); } } /** * Provide an interface to get instance objects (based on id) * @param id * @return */ public static Object getBean(String id) { return map.get(id); } }
Once the instantiation of the object is handed over to BeanFactory, we can use it as follows:
Transaction Control Problem Analysis
Verify the transfer exception manually in the business code of the transfer.Simulate an exception with a denominator of 0 between the transfer and transfer of two accounts.
accountDao.updateAccountByCardNo(to); int i = 1/0; accountDao.updateAccountByCardNo(from);
Then start the program and click Transfer (Li Dalei transfers 100 RMB to Hanmei), the following error will occur.
Now let's look at the database again
It was found that Han Meimei's account increased by 100 yuan, but Li Dalei's account did not decrease (both accounts originally had 10,000 yuan).
The reason for this problem is that because the Service layer does not have transaction control functions, errors in the transfer process (exceptions between inbound and outbound, inbound completed, outbound not in progress) can cause the above problems.
Transaction issues with databases are ultimately connected transactions
connection.commit() Submit a transaction
connection.rollback() Rollback transaction
In the case of bank transfer above, the two update s use two database connections, so they do not belong to the same transaction control.
Solution ideas:
From the above analysis, we can see that the reason for the problem is that two updates used two different connection connections.So to solve this problem, we need to have two updates connect using the same connection
Two update s belong to execution calls within the same thread, and we can bind a Connection to the current thread and use that connection for database operations related to the current thread (obtained from the current thread, used the connection for the first time, found that the current thread does not have one, obtained a connection from the connection pool to bind to the current thread)
On the other hand, transaction control is currently done at the Dao tier. We need to refer transaction control to the service tier (the service tier is where the business logic is executed, where methods from multiple Dao tiers may be invoked, and we need to have overall transaction control over the service tier's methods).
With these two ideas in mind, let's make code changes below.
Transaction Control Code Modification
Add ConnectionUtils Tool Class
ConnectionUtils
Add TransactionManager Transaction Management Class
TransactionManager
Add Agent Factory ProxyFactory
ProxyFactory
/** * Proxy Object Factory: Generates Proxy Objects */ public class ProxyFactory { private TransactionManager transactionManager; public void setTransactionManager(TransactionManager transactionManager) { this.transactionManager = transactionManager; } /** * Jdk Dynamic Proxy * @param obj Delegate Object * @return Proxy Object */ public Object getJdkProxy(Object obj) { //Get Proxy Object 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{ //Open transaction (close automatic commit of transaction) transactionManager.beginTransaction(); result = method.invoke(obj,args); //Submit Transaction transactionManager.commit(); }catch (Exception e) { e.printStackTrace(); //Rollback transaction transactionManager.rollback(); //Throwing exceptions makes it easier for upper-level servlet s to catch throw e; } return result; } }); } /** * Proxy object generation using cglib dynamic proxy * @param obj Delegate Object * @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{ //Open transaction (close automatic commit of transaction) transactionManager.beginTransaction(); result = method.invoke(obj,objects); //Submit Transaction transactionManager.commit(); }catch (Exception e) { e.printStackTrace(); //Rollback transaction transactionManager.rollback(); //Throwing exceptions makes it easier for upper-level servlet s to catch throw e; } return result; } }); } }
modifyBeans.xmlfile
beans
Modify the implementation of JdbcAccountDaoImpl
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 { //Get a connection from the connection pool // Connection con = DruidUtils.getInstance().getConnection(); //Modify to: Get a bound connection from the current thread 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 { //Get a connection from the connection pool // Connection con = DruidUtils.getInstance().getConnection(); //Modify to: Get a bound connection from the current thread 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; } }
Modify TransferServlet
TransferServlet
@WebServlet(name="transferServlet",urlPatterns = "/transferServlet") public class TransferServlet extends HttpServlet { //// 1. Instantiate service layer objects // private TransferService transferService = new TransferServiceImpl(); //Modified to get service layer objects from a Bean project // private TransferService transferService = (TransferService) BeanFactory.getBean("transferService"); //Obtaining delegate objects from a project (delegate objects enhance transaction control) 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 { //Set the character encoding of the requester 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. Call service layer methods transferService.transfer(fromCardNo,toCardNo,money); result.setStatus("200"); } catch (Exception e) { e.printStackTrace(); result.setStatus("201"); result.setMessage(e.toString()); } //Response resp.setContentType("application/json;charset=utf-8"); resp.getWriter().print(JsonUtils.object2Json(result)); } }
After the renovation, we test again and find that when there are errors in the transfer process, the transaction can be successfully controlled (no less money will be transferred to the account, no more money will be transferred to the account).
Why use proxy for transaction control?
Here we can consider the question, why should we use proxy to achieve transaction control?
If we don't use proxies, we need to write the code for transaction control in the service layer's TransferServiceImpl implementation.
public class TransferServiceImpl implements TransferService { //Best State private AccountDao accountDao; //Constructor transfer/set method transfer public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } @Override public void transfer(String fromCardNo, String toCardNo, int money) throws Exception { try{ //Open transaction (close automatic commit of transaction) 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); //Simulate exceptions int c = 1/0; accountDao.updateAccountByCardNo(from); //Submit Transaction TransactionManager.getInstance().commit(); }catch (Exception e) { e.printStackTrace(); //Rollback transaction TransactionManager.getInstance().rollback(); //Throwing exceptions makes it easier for upper-level servlet s to catch throw e; } } }
In this way, transaction control and specific business code are coupled, and if there are multiple methods that require transaction control functionality, we need to add them to each business method.This will result in a lot of duplicate code.So the idea of AOP is used here to implement transaction control through dynamic proxy.