Without IoC and AOP

Without Spring, we first manually implement the business logic and layer it:

  • DAO
  • Service
  • Controller

Introduction to IoC (Inversion of Control)

Inversion of Control (IoC) is a design principle used to achieve decoupling between components, and it is one of the most important concepts in object-oriented programming. The core idea of IoC is to transfer the control of objects from the caller to the framework or container, so that the dependency relationships between objects are managed by the container.


Features of IoC

Decoupling

IoC reduces coupling between modules by handing over the responsibility of dependency management and creation to the container, enhancing system maintainability and scalability.

Dynamic Dependency Management

The container dynamically injects dependencies into objects based on configuration or annotations, without hard-coded dependency relationships.

Flexibility

Object dependencies can be dynamically modified at runtime by simply changing the configuration, without modifying the code.


Application Scenarios of IoC

IoC is widely used in various software development frameworks and projects. Here are some typical applications:

  • Spring Framework: The Spring framework uses the IoC container to manage the lifecycle and dependencies of Beans
  • Guice and Dagger: Lightweight DI frameworks
  • Frontend frameworks (such as Angular): Managing dependencies between components and services through a dependency injection system

Advantages of IoC

  • Enhanced modularity: By reducing direct dependencies between modules, modular design is promoted
  • Improved testability: Dependency injection makes it easy to replace dependency objects, thereby supporting unit testing
  • Enhanced flexibility: Dependencies can be dynamically injected through configuration or annotations
  • Easier maintenance: Decoupling minimizes the impact when adding or modifying functionality

Limitations of IoC

  • Learning curve: Beginners need time to understand the concepts of IoC and DI
  • Runtime performance overhead: The IoC container parsing dependencies at runtime may introduce some performance overhead
  • Complexity: In large projects, excessive use of IoC may make configuration and dependency relationships complex

Utils

WzkDruidUtils

/**
 * Druid utility class
 * Uses singleton design pattern, static block initialization
 * @author wzk
 * @date 16:38 2024/11/18
 **/
public class WzkDruidUtils {

    private static DruidDataSource druidDataSource = new DruidDataSource();

    static {
        druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
        druidDataSource.setUrl("jdbc:mysql://172.16.1.130:3306/wzk-mybatis");
        druidDataSource.setUsername("hive");
        druidDataSource.setPassword("hive@wzk.icu");
    }

    private WzkDruidUtils() {

    }

    public static DruidDataSource getInstance() {
        return druidDataSource;
    }
}

WzkConnectionUtils

/**
 * Current thread SQL connector
 * Uses ThreadLocal to pass connection
 * @author wzk
 * @date 16:43 2024/11/18
 **/
public class WzkConnectionUtils {

    private final ThreadLocal<Connection> threadLocal = new ThreadLocal<>();

    public Connection getCurrentConnection() throws SQLException {
        Connection connection = threadLocal.get();
        if (null == connection) {
            connection = WzkDruidUtils.getInstance().getConnection();
            threadLocal.set(connection);
        }
        return connection;
    }
}

DAO Layer

WzkAccountDao

package wzk.dao;

import wzk.model.WzkAccount;

public interface WzkAccountDao {

    WzkAccount selectWzkAccount(String card) throws Exception;

    int updateWzkAccount(WzkAccount wzkAccount) throws Exception;

}

JdbcWzkAccountDaoImpl

public class JdbcWzkAccountDaoImpl implements WzkAccountDao {

    private WzkConnectionUtils wzkConnectionUtils;

    public void setWzkConnectionUtils(WzkConnectionUtils wzkConnectionUtils) {
        this.wzkConnectionUtils = wzkConnectionUtils;
    }


    @Override
    public WzkAccount selectWzkAccount(String card) throws Exception {
        Connection connection = wzkConnectionUtils.getCurrentConnection();
        String sql = "select * from wzk_account where card = ? limit 1";
        PreparedStatement prepareStatement = connection.prepareStatement(sql);
        prepareStatement.setString(1, card);
        ResultSet resultSet = prepareStatement.executeQuery();
        WzkAccount wzkAccount = new WzkAccount();
        while (resultSet.next()) {
            wzkAccount.setCard(resultSet.getString("card"));
            wzkAccount.setName(resultSet.getString("name"));
            wzkAccount.setMoney(resultSet.getInt("money"));
        }
        resultSet.close();
        prepareStatement.close();
        return wzkAccount;
    }

    @Override
    public int updateWzkAccount(WzkAccount wzkAccount) throws Exception {
        Connection connection = wzkConnectionUtils.getCurrentConnection();
        String sql = "update wzk_account set money =? where card =?";
        PreparedStatement prepareStatement = connection.prepareStatement(sql);
        prepareStatement.setInt(1, wzkAccount.getMoney());
        prepareStatement.setString(2, wzkAccount.getCard());
        int result = prepareStatement.executeUpdate();
        prepareStatement.close();
        return result;
    }
}

Service Layer

WzkTransferService

package wzk.service;

public interface WzkTransferService {

    void transfer(String fromCard, String toCard, int money) throws Exception;

}

WzkTransferServiceImpl

public class WzkTransferServiceImpl implements WzkTransferService {

    private WzkAccountDao wzkAccountDao;

    public void setWzkAccountDao(WzkAccountDao wzkAccountDao) {
        this.wzkAccountDao = wzkAccountDao;
    }


    @Override
    public void transfer(String fromCard, String toCard, int money) throws Exception {
        WzkAccount from = wzkAccountDao.selectWzkAccount(fromCard);
        WzkAccount to = wzkAccountDao.selectWzkAccount(toCard);
        from.setMoney(from.getMoney() - money);
        to.setMoney(to.getMoney() + money);
        int fromResult = wzkAccountDao.updateWzkAccount(from);
        int toResult = wzkAccountDao.updateWzkAccount(to);
        System.out.println("transfer fromResult: " + fromResult + " toResult: " + toResult);
    }
}

Controller Layer

WzkServlet

@WebServlet(name="wzkServlet", urlPatterns = "/wzkServlet")
public class WzkServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("=== WzkServlet doGet ===");

        // Without Spring's help, we need to manually create and maintain dependency relationships
        // Assemble DAO, DAO layer depends on ConnectionUtils and DruidUtils
        JdbcWzkAccountDaoImpl jdbcWzkAccountDaoImpl = new JdbcWzkAccountDaoImpl();
        jdbcWzkAccountDaoImpl.setConnectionUtils(new WzkConnectionUtils());

        // Assemble Service
        WzkTransferServiceImpl wzkTransferService = new WzkTransferServiceImpl();
        wzkTransferService.setWzkAccountDao(jdbcWzkAccountDaoImpl);

        // Execute business logic
        try {
            wzkTransferService.transfer("1", "2", 100);
        } catch (Exception e) {
            System.out.println("=== transfer error ====");
        }

        resp.setContentType("application/json;charset=utf-8");
        resp.getWriter().print("=== WzkServlet doGet ===");
    }

}

Test Run

Start the service and access the URL in a browser or Postman:

http://localhost:8999/wzkServlet

In a scenario without IoC and AOP, we need to manually create and maintain dependency relationships. This demonstrates the core value of IoC—transferring control of object creation and dependency management from application code to the container.