Mastering Spring AOP and JDK Dynamic Proxies: Common Issues and Fixes

This tutorial demonstrates how to create JavaBeans, DAO interfaces, and implementations, use JDK dynamic proxies to expose proxy classes, troubleshoot method‑level proxy failures, and apply Spring AOP with ProxyFactory or @Aspect, while also covering transaction pitfalls and best‑practice configurations.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Mastering Spring AOP and JDK Dynamic Proxies: Common Issues and Fixes

Environment: Spring Boot 2.3.10

Demo Classes

JavaBean:

public class User {
  private Integer id;
  private String name;
  public User(Integer id, String name) {
    this.id = id;
    this.name = name;
  }
}

DAO interface:

public interface UserDAO {
  User save(User user);
  User findUser(Integer id);
}

DAO implementation:

@Component
public class UserDAOImpl implements UserDAO {
  @Override
  public User save(User user) {
    this.findUser(user.getId());
    System.out.println("save method : " + user);
    return user;
  }

  @Override
  public User findUser(Integer id) {
    System.out.println("findUser method invoke...");
    return new User(id, "张三" + new Random().nextInt(10000));
  }
}

JDK Dynamic Proxy

Generate a proxy class to demonstrate method interception within the same class:

public static void main(String[] args) {
  System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
  UserDAO target = new UserDAOImpl();
  UserDAO dao = (UserDAO) Proxy.newProxyInstance(
    ProxyDemo.class.getClassLoader(),
    new Class<?>[] {UserDAO.class},
    new InvocationHandler() {
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before");
        method.invoke(target, args);
        return null;
      }
    }
  );
  context.set(dao);
  dao.save(new User(1, "田七"));
}

The property sun.misc.ProxyGenerator.saveGeneratedFiles saves the generated $Proxy0.class under %project_root%\com\sun\proxy. The generated proxy extends Proxy and implements UserDAO. Calls to save are intercepted ("before" is printed), while findUser is not.

To proxy findUser as well, the proxy instance is stored in a ThreadLocal and retrieved inside save:

public User save(User user) {
  ProxyDemo.currentProxy().findUser(user.getId());
  System.out.println("save method : " + user);
  return user;
}

Running this version shows both methods being invoked through the proxy.

Spring AOP Dynamic Proxy

Method 1 – Using ProxyFactory (JDK proxy by default, CGLIB if no interface):

public static void main(String[] args) {
  ProxyFactory proxyFactory = new ProxyFactory(new UserDAOImpl());
  proxyFactory.addInterface(UserDAO.class);
  proxyFactory.addAdvice(new MethodBeforeAdvice() {
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
      logger.info("调用之前");
    }
  });
  UserDAO dao = (UserDAO) proxyFactory.getProxy();
  User user = new User(1, "李四");
  dao.save(user);
}

Result: the "before" advice runs before save.

Method 2 – Defining an @Aspect:

@Aspect
@Component
public class UserAspect {
  private static Logger logger = LoggerFactory.getLogger(UserAspect.class);

  @Pointcut("execution(* com.pack.dao..*.*(..))")
  private void log() {}

  @Before("log()")
  public void beforeLog() {
    logger.info("方法执行之前操作...");
  }
}

Register UserDAOImpl as a Spring bean and inject UserDAO (which is the proxy) to call other methods via the proxy:

@Component
public class UserDAOImpl implements UserDAO {
  @Resource
  private UserDAO userDAO;

  @Override
  public User save(User user) {
    userDAO.findUser(user.getId());
    System.out.println("save method : " + user);
    return user;
  }
}

Test class:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = SpringBootAopApplication.class)
public class SpringBootAopApplicationTests {
  @Resource
  private UserDAO userDAO;

  @Test
  public void testAop() {
    User user = new User(1, "李四");
    userDAO.save(user);
  }
}

Important configuration: enable proxy exposure with ProxyFactory.setExposeProxy(true) or spring.aop.proxy-target-class=true so that AopContext.currentProxy() can be used.

When a non‑transactional method calls a @Transactional method directly, the transaction will not be applied. Use AOP proxy (e.g., AopContext.currentProxy()) or move the transactional method to another bean.

The next approach is absolutely horrendous, and we hesitate to point it out, precisely because it is so horrendous. You can (painful as it is to us) totally tie the logic within your class to Spring AOP.

Binding business logic tightly to Spring AOP is discouraged due to strong coupling.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

transactionaopspringSpring BootDynamic Proxycglib
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.