Handwritten implementation of IOC and AOP through a case of bank transfer

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.



Keywords: Java SQL xml Database encoding

Added by mrskhris on Tue, 02 Jun 2020 14:16:52 +0300