Previous Progress

In the previous section, we completed the basic code. In this section, we continue with the implementation of IoC.

IoC Implementation

At this point, we need a management container for Beans. When we need to create an object with new, we can directly retrieve it from the container. However, we need to initialize these objects when the application starts (or lazily), so we need XML to tell the container what to load.

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: These lightweight DI frameworks are also popular in Java projects.
  • Frontend frameworks (such as Angular): Angular also implements IoC.

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, allowing the system to adapt to changes without code modifications.
  • 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.

Resources

beans.xml

First, let’s write an XML to store our Bean information:

<?xml version="1.0" encoding="UTF-8" ?>
<!-- The BeanFactory class processes this content -->
<beans>
    <!-- WzkConnectionUtils managed by the container -->
    <bean id="wzkConnectionUtils" class="wzk.utils.WzkConnectionUtils"></bean>
    <!-- Depends on utility class WzkConnectionUtils -->
    <!-- id is the name to put into the container -->
    <bean id="wzkAccountDao" class="wzk.dao.impl.JdbcWzkAccountDaoImpl">
        <!-- name is the member variable name, ref is the reference to get the object from the container -->
        <property name="WzkConnectionUtils" ref="wzkConnectionUtils"/>
    </bean>
    <!-- Depends on WzkAccountDao -->
    <bean id="wzkTransferService" class="wzk.service.impl.WzkTransferServiceImpl">
        <!-- name is the member variable name, ref is the reference to get the object from the container -->
        <property name="WzkAccountDao" ref="wzkAccountDao"></property>
    </bean>
</beans>

Note: beans.xml needs to be placed in the resources directory.

Proxy

BeanFactory

public class BeanFactory {

    private BeanFactory() {}

    /**
     * Global container, all objects are stored here
     */
    private static Map<String, Object> map = new HashMap<>();

    static {
        // Process the XML, refer to our previous hand-written MyBatis part
        // Static block for initialization
        InputStream resourceAsSteam = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
        SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read(resourceAsSteam);
            Element rootElement = document.getRootElement();
            List<Element> beanList = rootElement.selectNodes("//bean");
            for (Element element : beanList) {
                // Process each Bean element
                String id = element.attributeValue("id");
                String clazz = element.attributeValue("class");
                // Get object via reflection
                Class<?> aClass = Class.forName(clazz);
                Object object = aClass.newInstance();
                // Save to container
                map.put(id, object);
            }

            List<Element> propertyList = rootElement.selectNodes("//property");
            for (Element element : propertyList) {
                // Process each dependency
                String name = element.attributeValue("name");
                String ref = element.attributeValue("ref");
                // Get the parent node, its properties are its dependencies
                Element parentElement = element.getParent();
                String parentId = parentElement.attributeValue("id");
                // Get object by parent's ID
                Object parentObject = map.get(parentId);
                // Get all methods of the parent: Getters, Setters, etc.
                Method[] methods = parentObject.getClass().getMethods();
                for (Method method : methods) {
                    // If it's a set method, we call it to assign its dependency to the parent
                    if (method.getName().equalsIgnoreCase("set" + name)) {
                        method.invoke(parentObject, map.get(ref));
                    }
                }
                // Remember to update the container's content
                map.put(parentId, parentObject);
            }
            System.out.println("BeanFactory initialized map:" + map);
        } catch (Exception e) {
            System.out.println("BeanFactory initialization failed");
            e.printStackTrace();
        }
    }

    public static Object getBean(String id) {
        return map.get(id);
    }

}

Controller

WzkServlet

Let’s modify the WzkServlet:

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

    // ========================== 2 ==========================
    // Handled by BeanFactory
    private WzkTransferService wzkTransferService = (WzkTransferService) BeanFactory.getBean("wzkTransferService");
    // =======================================================

    @Override
    protected void doGet(javax.servlet.http.HttpServletRequest req, javax.servlet.http.HttpServletResponse resp) throws javax.servlet.ServletException, IOException {
        System.out.println("=== WzkServlet doGet ===");
        // ======================= 1 =============================
        // 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) {
            e.printStackTrace();
            System.out.println("=== transfer error ====");
        }
        resp.setContentType("application/json;charset=utf-8");
        resp.getWriter().print("=== WzkServlet doGet ===");
    }

}

WzkServlet(2)

There’s another similar approach that can also execute successfully.

// ========================== 3 ==========================
// Another approach, the same result
private WzkTransferService wzkTransferService;
@Override
public void init() throws ServletException {
    super.init();
    this.wzkTransferService = (WzkTransferService) BeanFactory.getBean("wzkTransferService");
}
// ======================================================

Test Run

After running the code, you can see the following in the console:

BeanFactory initialized map:{wzkTransferService=wzk.service.impl.WzkTransferServiceImpl@5dfdd67c, wzkAccountDao=wzk.dao.impl.JdbcWzkAccountDaoImpl@380d9fc0, wzkConnectionUtils=wzk.utils.WzkConnectionUtils@2d150354}
=== WzkServlet doGet ===
Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.
Nov 18, 2024 6:24:24 PM com.alibaba.druid.pool.DruidDataSource info
INFO: {dataSource-1} inited
transfer fromResult: 1 toResult: 1

Check Results

The database results have changed. Of course, the printed content also shows that our code is working normally.

Current Issues

“Although we have implemented a simple IoC, for the current business needs, we still need to control transactions. At this point, we need to implement a transaction manager, which will be controlled using ThreadLocal, similar to database approaches.”