This is issue is somewhat hard to explain. I created a small project where this issue can be demonstrated.
Given following classes:
package com.flightkeys.cglibbug.base;
public class BaseClass {
private final ObjectMapper mapper = new ObjectMapper();
String serialize(Object value) {
return serialize(getMapper(), value);
}
private ObjectMapper getMapper() {
return mapper;
}
private String serialize(ObjectMapper mapper, Object value) {
return mapper.writeToString(value);
}
}
package com.flightkeys.cglibbug.derived;
import com.flightkeys.cglibbug.base.BaseClass;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;
@Component
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
public class DerivedClass extends BaseClass {
}
If we pass around instance of DerivedClass
and on that instance BaseClass.serialize
is called, then return mapper.writeToString(value);
will throw null pointer exception.
It seems that CGLIB is not proxying the String serialize(Object value)
method. However, if we move DerivedClass
into same package or either String serialize(Object value)
or private ObjectMapper getMapper()
is made protected
, then it works.
NOTE: We noticed this when upgrading from spring-boot 2.1.18 to 2.2.13, but it is also reproducible on 2.7.2.
Comment From: snicoll
This is the expected behavior. You can't proxy a private method, see the documentation.
Comment From: sbrannen
This is the expected behavior. You can't proxy a private method, see the documentation and
That is indeed the expected behavior.
However, I can see how this particular scenario can be somewhat confusing.
CGLIB can actually proxy a package-private method, but the sample application does not work as-is, because BaseClass.serialize(Object)
is "effectively private" relative to DerivedClass
, and the CGLIB generated subclass is in the same package as DerivedClass
.
That explains why the sample application works without modifying the visibility of BaseClass.serialize(Object)
when moving BaseClass
to the same package as DerivedClass
, and it also explains why it works if the visibility of BaseClass.serialize(Object)
is changed to protected
.
Since the documentation explicitly only mentions that private
methods cannot be proxied, I think it's worth updating the documentation to mention that "effectively private" methods also cannot be proxied.
In light of that, I am reopening this issue to improve the documentation.