Backend Development 20 min read

Understanding AOP, Proxies, and Dynamic vs. Static Proxy Mechanisms in Java Spring

This article explains how Aspect‑Oriented Programming (AOP) can replace repetitive try‑catch blocks in Dubbo services, introduces core AOP concepts such as JoinPoint, Pointcut, and Advice, and compares static, JDK dynamic, and CGLIB dynamic proxy implementations with practical Java code examples.

Full-Stack Internet Architecture
Full-Stack Internet Architecture
Full-Stack Internet Architecture
Understanding AOP, Proxies, and Dynamic vs. Static Proxy Mechanisms in Java Spring

In many Java backend services, especially those using Dubbo, developers wrap each method in a try...catch block to return a unified ServiceResultTO , which leads to duplicated and hard‑to‑maintain code. AOP solves this by allowing cross‑cutting concerns (e.g., error handling, logging, timing) to be injected without modifying the original source.

The key AOP concepts are:

JoinPoint : a point during program execution (method call, exception, etc.) where advice can be applied.

Pointcut : a predicate that selects which join points should be intercepted, expressed with AspectJ pointcut language.

Advice : the actual code that runs at the selected join points, with types such as Before , After , AfterReturning , AfterThrowing , and Around .

Example of a simple service and a manual try...catch implementation:

public class ServiceResultTO
implements Serializable {
    private Boolean success;
    private String message;
    private T data;
}

public interface TestService {
    ServiceResultTO
test();
}

public class TestServiceImpl implements TestService {
    @Override
    public ServiceResultTO
test() {
        try {
            return ServiceResultTO.buildSuccess(Boolean.TRUE);
        } catch (Exception e) {
            return ServiceResultTO.buildFailed(Boolean.FALSE, "执行失败");
        }
    }
}

Using AOP, the same logic can be expressed with an annotation and a single aspect:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface GlobalErrorCatch {}

@Aspect
@Component
public class GlobalErrorAspect {
    @Pointcut("@annotation(com.example.demo.annotation.GlobalErrorCatch)")
    private void globalCatch() {}

    @Around("globalCatch()")
    public Object handle(ProceedingJoinPoint p) throws Throwable {
        try {
            return p.proceed();
        } catch (Exception e) {
            System.out.println("执行错误" + e);
            return ServiceResultTO.buildFailed("系统错误");
        }
    }
}

Static proxy requires a hand‑written proxy class for each target, leading to boilerplate and limited reuse. Dynamic proxies generate the proxy at runtime:

JDK Dynamic Proxy (interface‑based)

public class ProxyFactory {
    private Object target;
    public ProxyFactory(Object target) { this.target = target; }
    public Object getProxyInstance() {
        return Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            (proxy, method, args) -> {
                System.out.println("计算开始时间");
                Object result = method.invoke(target, args);
                System.out.println("计算结束时间");
                return result;
            });
    }
}

JDK proxies require the target to implement an interface, which is a limitation for many concrete classes.

CGLIB Dynamic Proxy (subclass‑based)

public class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("目标类增强前!!!");
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("目标类增强后!!!");
        return result;
    }
}

public class CglibProxyDemo {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(RealSubject.class);
        enhancer.setCallback(new MyMethodInterceptor());
        RealSubject proxy = (RealSubject) enhancer.create();
        proxy.request();
    }
}

CGLIB works by creating a subclass of the target class and overriding non‑final methods; it does not require interfaces but cannot proxy final classes or final methods. It achieves higher performance than JDK proxies by using the FastClass mechanism instead of reflection.

In summary, AOP combined with dynamic proxies (especially CGLIB in Spring) provides a clean, maintainable way to handle cross‑cutting concerns such as error handling, logging, and performance monitoring without polluting business logic.

JavaProxyaopSpringDynamicProxyCGLIB
Full-Stack Internet Architecture
Written by

Full-Stack Internet Architecture

Introducing full-stack Internet architecture technologies centered on Java

0 followers
Reader feedback

How this landed with the community

login 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.