On the #21 review page you scored 41/66 (62%) across the eight drill blocks. No block was at full marks. That's a similar rate to the paper itself (68%), which means reading the explanations once didn't change the answers. This page takes a different angle on each fact: a small rule you can apply, a code sample you could run, and new questions that ask the same thing in a different way.
| Block on #21's page | Score | What you missed |
|---|---|---|
| ①c Lifecycle + hooks | 4/9 | Private @PostConstruct · constraints · non-web Environment · BFPP classes · where proxies are made |
| ③ Testing | 3/6 | @MockBean (three true) · what @ContextConfiguration loads · the Feign fix |
| Mixed drill | 6/10 | Which modify bytecode · protected @PreDestroy · @Bean(initMethod, destroyMethod) · final class |
| ⓪ The repeats | 5/8 | Non-web Environment · when BPP modifies · embedded server |
①b DI and @Autowired | 8/12 | @Autowired's attributes · which cycles throw · name fallback · Boot 2.6 cycles |
| ② Spring Boot | 4/6 | Tomcat timeout property · excludeName |
| ①a Proxies, AOP, weaving | 7/10 | -javaagent for LTW · limits shared by both proxies · what a proxy does |
| ④ Spring MVC | 4/5 | Which annotations bind a parameter |
Each finished block is recorded here in this browser. Reload to retake. Do the six fact blocks first, then retry all 25. Do the retry again tomorrow, without rereading. If you get it right a day later, the fact has stuck.
| Drill block | Qs | Last | Best | Runs | Last done |
|---|---|---|---|---|---|
| Loading… | |||||
Environment: non-web has two sources — 2 misses
6 · Boot config + test config — 6 misses
Retry all 25, as they were
@PostConstruct private void init() · constraints (with "not static") · @PreDestroy protected · @Bean(initMethod, destroyMethod)You got the two-option version right in the repeats block, then missed three other questions that test the same rule. So the wording "must be public" is now familiar, but the rule behind it isn't. Here's the rule, shown in code instead of in a table:
@Component
class Probe {
@PostConstruct
private void init() { System.out.println("init ran"); } // runs
@PreDestroy
protected void bye() { System.out.println("bye ran"); } // runs
}
Both lines print. Spring finds these methods by reflection (the
CommonAnnotationBeanPostProcessor), calls ReflectionUtils.makeAccessible(method), then
method.invoke(bean). makeAccessible switches off the visibility check, so
private, protected, package-private and public all work the same.
| Ask yourself | Answer | So the rule is |
|---|---|---|
| What would Spring pass as an argument? | Nothing, it has nothing to give | No parameters |
| Who reads the return value? | Nobody | void |
| Which object is it called on? | This bean instance | Not static |
Does reflection care about private? | Not after makeAccessible | Any visibility |
The same applies to @Bean(initMethod = "start", destroyMethod = "stop").
It's a third way to get the same two callbacks: start() runs after DI (after
@PostConstruct and afterPropertiesSet()), and stop() runs when the context
closes. It has nothing to do with scanning, and it doesn't run around every method call.
private doesn't stop it.| Question | Spring AOP (JDK or CGLIB proxy) | AspectJ weaving (compile-time or LTW) |
|---|---|---|
| 1. Does the original class change? | No. A second object is put in front of it | Yes. Its bytecode is changed |
2. Does this.other() get advised? | No. this is the target, not the proxy | Yes. The advice is in the method body itself |
Needs a -javaagent? | No | Load-time, in a plain JVM: yes (spring-instrument.jar + META-INF/aop.xml). Compile-time: no, uses ajc |
| Anything on disk changed? | No | Compile-time: yes. Load-time: no, only in memory |
Then, for proxies only, which kind? Picture what each one has to be in Java:
// JDK proxy: a new class that IMPLEMENTS your interfaces
class $Proxy12 implements Payer { ... } // needs an interface
// CGLIB proxy: a new class that EXTENDS your class
class Billing$$EnhancerBySpringCGLIB$$1a2b extends Billing { ... } // can't if Billing is final
// can't override final methods
The JDK proxy needs an interface. CGLIB is blocked by final. Only two limits hit
both: self-invocation, and private methods (neither kind of proxy can override them). So a
public final class Billing with no interface can't be proxied either way, and startup fails. It
isn't silently skipped, because there's no proxy to skip the advice on.
this. calls and private methods.@Autowired picks a bean, and which cycles fail 4 misses@Autowired's attributes · name fallback · which cycles throw · Boot 2.6 cycles| Step | Spring asks | If yes |
|---|---|---|
| 1 | How many beans of this type? | 0 → NoSuchBeanDefinitionException (or null if required = false). 1 → inject it |
| 2 | Several. Is there a @Qualifier? | Narrow to it |
| 3 | Is exactly one @Primary? | Inject it |
| 4 | Does a bean's name match the field / parameter name? | Inject it |
| 5 | Still more than one | NoUniqueBeanDefinitionException |
@Autowired takes part with one attribute, required. Narrowing by
name is the job of @Qualifier("x") (or step 4). There's no name, value or
qualifier attribute on @Autowired.
Cycles: ask "does an instance exist yet?"
| Cycle | When A asks for B, does an A object exist? | Plain Spring | Boot 2.6+ default |
|---|---|---|---|
| Singletons, setter/field | Yes, it's constructed, just not filled in | Resolved (early reference) | Fails |
| Singletons, constructor | No, A's constructor is still waiting | Throws | Throws |
| Prototypes | No cache for half-built prototypes | Throws | Throws |
Boot 2.6 turned the setter case off by default: spring.main.allow-circular-references=false.
@Lazy on one of the constructor parameters breaks any cycle, because Spring injects a lazy proxy instead
of the real bean.
interface BeanFactoryPostProcessor {
void postProcessBeanFactory(ConfigurableListableBeanFactory f); // gets the PLANS
}
interface BeanPostProcessor {
Object postProcessBeforeInitialization(Object bean, String name); // gets a REAL OBJECT
Object postProcessAfterInitialization(Object bean, String name); // and can return a replacement
}
Object bean, so the bean already exists. "Before instantiation" can't be BPP.
It runs after the constructor and DI, either side of the init callbacks.postProcessAfterInitialization returns an object, and that object is what the
container stores. That's how AOP swaps your bean for a proxy: the auto-proxy creator is a BPP, and it returns
the proxy from its after-init hook.${…} placeholders.Recognising them by name: Spring's names say what they are.
…Configurer classes that edit definitions (PropertySourcesPlaceholderConfigurer,
PropertyOverrideConfigurer) are BFPPs. Classes named …BeanPostProcessor
(AutowiredAnnotationBeanPostProcessor, CommonAnnotationBeanPostProcessor) are BPPs,
which is how they can set fields and call @PostConstruct on real objects.
Environment has exactly two property sources 2 misses · both non-webOn #14 you gave a non-web app JNDI and the servlet parameters. These two misses suggest that hasn't changed, probably on JNDI. This is an inference: the copy doesn't show what you clicked. Use the application server as the test:
| Source | Where it comes from | Exists in a plain java -jar / main() app? |
|---|---|---|
systemProperties | The JVM (-Dx=y) | Yes — every JVM has them |
systemEnvironment | The OS (export X=y) | Yes — every process has them |
servletContextInitParams | The servlet container | No container |
servletConfigInitParams | The servlet container | No container |
jndiProperties | java:comp/env, provided by the app server | No app server |
StandardEnvironment (non-web) adds the first two. StandardServletEnvironment
(web) adds the other three on top. Profiles are available in both, because they're part of the
Environment interface itself.
excludeName · @MockBean · @ContextConfiguration · the Feign fix · parameter binding| Need | Real | Trap | Why the trap fails |
|---|---|---|---|
| Different server | Exclude Tomcat starter + add Jetty/Undertow starter | "…for an external server" | Every starter is embedded. External = WAR + SpringBootServletInitializer |
| Timeout | server.tomcat.connection-timeout | server.timeout, server.connection-timeout | One doesn't exist; the other was removed in 2.3. Timeouts are per server |
| Exclude a class that's not on the classpath | excludeName = "a.b.FooAutoConfiguration" | exclude = Foo.class | A class literal for a missing class won't compile. A string always does |
| Extra auto-config in a slice | @ImportAutoConfiguration(X.class) | @ContextConfiguration(classes = X.class) | @ContextConfiguration loads plain config classes / XML and ignores auto-config conditions and ordering |
| Avoid loading the whole app | Keep the slice + @ImportAutoConfiguration | Switch to @SpringBootTest | Works, but it's the opposite of a slice test, so it isn't the best fix |
@MockBean | Class or field · adds to the context · defines a new bean if none exists | "Same as @Mock" | @Mock has no Spring context. Three of the four options are true. You've stopped at two three times |
| Bind a parameter | @RequestHeader, @CookieValue, @ModelAttribute, @RequestParam, @PathVariable, @RequestBody | @ResponseBody, @RequestMapping | Those two go on a method or class: one writes the response, the other maps the URL |
@ModelAttribute is the easiest one to doubt. On a parameter it builds an object from
form or query fields, as in save(@ModelAttribute Order order). That's binding.
@ImportAutoConfiguration. @MockBean = replace or add.These are the exact 25 drills you missed on the #21 page, reshuffled. Do this block after the six above, and again tomorrow without rereading. 25/25 tomorrow means the facts have stuck.