The full picture
| JDK dynamic proxy | CGLIB | |
|---|---|---|
| Requires | ≥1 interface | Non-final class, non-private methods |
| Proxy type | implements interfaces only | subclass of target |
| Inject by concrete class | ❌ ClassCastException risk | ✅ |
| final methods | n/a (interface) | silently NOT intercepted ⚠️ |
| Construction | reflection-based | bytecode generation (objenesis avoids ctor side effects) |
@Service
public class ReportService {
@Transactional
public void generateAll() {
for (var id : ids) generateOne(id); // ❌ this.generateOne — direct call
}
@Transactional(propagation = REQUIRES_NEW) // silently IGNORED above
public void generateOne(long id) { … }
}
// Fixes, best first:
// 1. Move generateOne to a separate bean (design-honest: it IS a separate concern)
// 2. Inject self-proxy: private final ReportService self; (with @Lazy)
// then: self.generateOne(id);
// 3. AspectJ weaving — heavy machinery, rarely worth it just for this