The way Spring uses CGLIB proxies is by a delegation pattern, see this answer for a schematic code sample about how this is implemented.
Please understand that when creating the proxy, only method delegation is being considered. The proxy object's instance fields will not be initialised, because usually at some point the proxy calls the delegate's method. Therefore, the delegate will transparently access its own (initialised) fields.
In case of a final
method, however, no proxy method can be generated, because final methods cannot be overridden. I.e., in this case the original method is called, but when accessing fields via e.g. return myField
(a shorthand for return this.myField
), this
is the proxy instance, because there was no method call delegation to the original object. This explains why the result is null
, 0
or false
, depending on the field type.
package de.scrum_master.spring.q72993106;
import org.springframework.stereotype.Component;
@Component
public class MyComponent {
String food = "bread";
String beverage = "water";
public final String eat() {
return food;
}
public String drink() {
return beverage;
}
}
Add an aspect as a simple way to trigger proxy creation:
package de.scrum_master.spring.q72993106;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class MyAspect {
@Before("execution(* MyComponent.*())")
public void myAdvice(JoinPoint jp) {
System.out.println(jp);
}
}
package de.scrum_master.spring.q72993106;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@SpringBootApplication
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class DemoApplication {
public static void main(String[] args) throws Throwable {
try (ConfigurableApplicationContext appContext = SpringApplication.run(DemoApplication.class, args)) {
doStuff(appContext);
}
}
private static void doStuff(ConfigurableApplicationContext appContext) {
MyComponent myComponent = appContext.getBean(MyComponent.class);
System.out.println("Eating " + myComponent.eat());
System.out.println("Drinking " + myComponent.drink());
}
}
Running this application yields the following console log:
Eating null
execution(String de.scrum_master.spring.q72993106.MyComponent.drink())
Drinking water
See? You get the expected value for the non-final method, because delegation to the original object takes place (and the aspect kicks in), but for the final method the result is null
, as explained above.