ExerciseWarm-up
Warm-up
Build both kinds of proxy
10 minjunior1–8 yrs
One concept, guided. Near-impossible to fail.
What this teaches
- A JDK dynamic proxy implements the interface; a CGLIB proxy subclasses the class
- The proxy is a different object, so it is not an instance of your class
- A subclass can only advise what a subclass can override
- The compiler enforces the final-method limit, so you can see it rather than trust it
Starter
Starter.java
import java.lang.reflect.*;
import java.util.*;
/**
* Warm-up: Spring AOP is two mechanisms, and you can build both by hand.
*
* A JDK dynamic proxy implements an interface and holds your object inside it.
* A CGLIB proxy is a generated subclass that overrides your methods. Every
* limit of Spring AOP falls out of which one you are looking at, so the fastest
* way to know the limits is to build both.
*/
public class Starter {
/* ─────────── kind one: a JDK dynamic proxy (done for you) ─────────── */
interface AuditService {
String record(String action);
void recordAll(List<String> actions);
}
static class RealAuditService implements AuditService {
@Override
public String record(String action) {
System.out.println(" writing " + action);
return "ok:" + action;
}
@Override
public void recordAll(List<String> actions) {
// Called on `this`. Note that carefully before you run anything.
for (String a : actions) record(a);
}
}
static AuditService advised(AuditService target, String aspect) {
return (AuditService) Proxy.newProxyInstance(
AuditService.class.getClassLoader(),
new Class<?>[] { AuditService.class },
(proxy, method, args) -> {
System.out.println(" " + aspect + " → " + method.getName());
Object result = method.invoke(target, args);
System.out.println(" " + aspect + " ← " + method.getName());
return result;
});
}
/* ─────────── kind two: a subclass, which is what CGLIB writes ─────────── */
static class ReportService {
public void generate(String quarter) {
System.out.println(" generating " + quarter);
}
public final void archive(String quarter) {
System.out.println(" archiving " + quarter);
}
private void checksum(String quarter) {
System.out.println(" checksum " + quarter);
}
public void publish(String quarter) {
checksum(quarter);
System.out.println(" published " + quarter);
}
}
// TODO 3: write AdvisedReportService here — a subclass of ReportService
// that holds a target and prints " audit → name" before and
// " audit ← name" after every method it is able to override.
//
// static class AdvisedReportService extends ReportService { ... }
public static void main(String[] args) {
AuditService proxy = advised(new RealAuditService(), "audit");
// TODO 1: predict both of these, in writing, before you run the file.
//
// proxy.getClass().getSimpleName() → ?
// proxy instanceof RealAuditService → ?
//
// Print them. Then answer: why would
// @Autowired RealAuditService audit;
// fail to start an application whose bean is this proxy?
System.out.println("recordAll, which loops and calls record itself:");
proxy.recordAll(List.of("login", "logout"));
// TODO 2: count the "audit →" lines above. How many did you expect?
// Which calls were not advised, and what object were they made on?
// TODO 4: once AdvisedReportService exists, call generate, archive and
// publish through it. One of the three gets no advice at all, and one
// is advised but does something unadvised inside it. Name both, and
// say why each is invisible to a subclass.
// TODO 5: now try adding
// @Override public void archive(String quarter) { ... }
// to your subclass. Read the compiler error. That error is the entire
// reason @Transactional on a final method silently does nothing.
}
}Run it locally:
cd exercises/java/spring-aop/aop-proxies/01-warmup
javac Starter.java -d /tmp/out && java -cp /tmp/out StarterDone when
- You predicted the proxy's class name and instanceof result before running
- You wrote a subclass proxy and watched one method get no advice at all
- You read the compiler error for overriding a final method
- You can say in one sentence why @Autowired on the concrete class can fail
← Back to How does Spring AOP work, and what are its limits?