Overview

This article mainly introduces how to manually implement AOP (Aspect-Oriented Programming) on top of the business from the previous section. Note that this AOP does not yet have extensibility; it is a simple implementation to help us better control transactions.


AOP (Aspect-Oriented Programming)

AOP (Aspect-Oriented Programming) is a programming paradigm that improves code modularity by separating concerns, especially functionalities related to cross-cutting concerns. These cross-cutting concerns refer to features not directly related to business logic but that need to span multiple modules, such as logging, security verification, transaction management, etc.


AOP Core Concepts

Aspect

The Aspect is the most important concept in AOP. It is a modularization of a certain cross-cutting concern. An aspect defines a feature unrelated to core business logic (such as logging, performance monitoring, etc.) and applies these features to various parts of the program.

Joinpoint

A Joinpoint refers to a place in the program where an aspect can be inserted, usually method calls, method execution, exception throwing, etc.

Advice

Advice is the specific behavior or operation that an aspect executes at a Joinpoint. There are different types of advice:

  • Before Advice: Executed before the target method
  • After Advice: Executed after the target method completes, regardless of success or failure
  • Around Advice: Can control both before and after the target method execution
  • Throws Advice: Executed after the target method throws an exception
  • AfterReturning Advice: Executed after the target method returns normally

Pointcut

A Pointcut is an expression that defines where advice should execute. It specifies which Joinpoints need to be intercepted.

Weaving

Weaving is the process of applying aspects to target objects. Common weaving methods include:

  • Compile-time weaving (e.g., using AspectJ compiler)
  • Class-loading time weaving
  • Runtime weaving (Spring AOP)

Resources Configuration

<?xml version="1.0" encoding="UTF-8" ?>
<!-- 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>

    <!-- WzkTransactionManager depends on WzkConnectionUtils -->
    <bean id="wzkTransactionManager" class="wzk.utils.WzkTransactionManager">
        <property name="WzkConnectionUtils" ref="wzkConnectionUtils"></property>
    </bean>

    <!-- ProxyFactory depends on WzkTransactionManager -->
    <bean id="proxyFactory" class="wzk.factory.ProxyFactory">
        <property name="WzkTransactionManager" ref="wzkTransactionManager"></property>
    </bean>
</beans>

WzkTransactionManager (Transaction Controller)

/**
 * Transaction controller, simple encapsulation of JDBC operations
 * @author wzk
 * @date 11:31 2024/11/19
**/
public class WzkTransactionManager {

    private WzkConnectionUtils wzkConnectionUtils;

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

    public void beginTransaction() throws SQLException {
        wzkConnectionUtils.getCurrentConnection().setAutoCommit(false);
    }

    public void commit() throws SQLException {
        wzkConnectionUtils.getCurrentConnection().commit();
    }

    public void rollback() throws SQLException {
        wzkConnectionUtils.getCurrentConnection().rollback();
    }

}

ProxyFactory (Proxy Factory)

/**
 * Proxy factory + singleton pattern
 * Reference proxy design pattern to implement transaction control
 * Mainly implements dynamic proxy approach
 * @author wzk
 * @date 11:33 2024/11/19
**/
public class ProxyFactory {

    private WzkTransactionManager wzkTransactionManager;

    public void setWzkTransactionManager(WzkTransactionManager wzkTransactionManager) {
        this.wzkTransactionManager = wzkTransactionManager;
    }

    /**
     * Dynamic proxy - JDK implementation
     * @author wzk
     * @date 11:35 2024/11/19
    **/
    public Object getJdkProxy(Object object) {
        return Proxy.newProxyInstance(
                object.getClass().getClassLoader(),
                object.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Object result;
                        // Begin transaction
                        wzkTransactionManager.beginTransaction();
                        // Execute original method
                        try {
                            result = method.invoke(object, args);
                        } catch (Exception e) {
                            // Rollback
                            wzkTransactionManager.rollback();
                            throw e;
                        }
                        // Commit transaction
                        wzkTransactionManager.commit();
                        return result;
                    }
                }
        );
    }

    /**
     * Dynamic proxy - CGLIB implementation
     * @author wzk
     * @date 13:57 2024/11/19
    **/
    public Object getCglibProxy(String object) {
        return Enhancer.create(
                object.getClass(),
                new MethodInterceptor() {
                    @Override
                    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                        Object result;
                        wzkTransactionManager.beginTransaction();
                        try {
                            result = method.invoke(object, args);
                        } catch (Exception e) {
                            wzkTransactionManager.rollback();
                            throw e;
                        }
                        wzkTransactionManager.commit();
                        return result;
                    }
                }
        );
    }
}

WzkServlet (Controller)

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

    // Get the proxied object with transaction AOP support
    private WzkTransferService wzkTransferService;

    @Override
    public void init() throws ServletException {
        super.init();
        // Get ProxyFactory from BeanFactory to proxy and extend certain classes
        // The extended feature: transaction commit and rollback
        ProxyFactory proxyFactory = (ProxyFactory) BeanFactory.getBean("proxyFactory");
        this.wzkTransferService = (WzkTransferService) proxyFactory.getJdkProxy(
                BeanFactory.getBean("wzkTransferService"));
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("=== WzkServlet doGet ===");
        // Execute business logic
        System.out.println("wzkTransferService: " + wzkTransferService);
        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 ===");
    }

}

Test Run 1 (Normal Execution)

Start the project for testing. Here we test the smooth execution case. You can see the execution proceeded smoothly, and the database operations were also normal.


Test Run 2 (Exception Rollback)

Here we test the case when a problem occurs during execution and rollback is needed. We need to add an exception somewhere in the transfer code of WzkTransferServiceImpl, such as 1 divided by 0:

@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);

    // Intentionally create an exception
    int i = 1 / 0;

    int toResult = wzkAccountDao.updateWzkAccount(to);
    System.out.println("transfer fromResult: " + fromResult + " toResult: " + toResult);
}

After execution, the database data should not change (because the transaction manager rolled back).


Summary

This article implemented transaction control by manually implementing AOP. Although this simple implementation does not have extensibility, it can help us better understand AOP principles and how transaction management works. Subsequent development can build upon this for more complex AOP functionality extensions.