行为上的差异是由 Spring 框架完成的代理造成的。当某个 bean 需要进行任何特殊处理(在本例中为缓存)时,就会在运行时创建该 bean 的代理。 Spring中有两种类型的代理技术 -基于 JDK 和 CGLIB 的代理 https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#aop-pfb-proxy-types。请阅读共享的文档链接以了解更多详细信息。
在共享的示例代码中,CGLIB 代理发挥了作用(线索:MyService
不实现接口)。 CGLIB 创建的运行时子类将具有原始类的所有字段,但根据数据类型将保留 null/默认值(此处false
)。此外,对代理的方法调用也被委托给原始实例方法。
对代码进行更改后将提供有关其工作原理的更多详细信息。
Change 1:打印object.getClass()
as well
@Service
public class MyService {
private Boolean fieldOfMyService = false;
public void printFieldOfMyService() {
System.out.println("fieldOfMyService:" + this.getClass()+" : "+fieldOfMyService);
}
@Cacheable("noOpMethod")
public void noOpMethod() {
}
}
测试班
@SpringBootTest(classes = { MyApplication.class })
public class MyServiceTest {
@Autowired
private MyService myService;
@Test
public void test() throws Exception {
myService.printFieldOfMyService();
boolean fieldOfMyService = (boolean) FieldUtils.readField(myService, "fieldOfMyService", true);
System.out.println(
"fieldOfMyService via reflection before change:" + myService.getClass() + " " + fieldOfMyService);
FieldUtils.writeField(myService, "fieldOfMyService", true, true);
boolean fieldOfMyServiceAfter = (boolean) FieldUtils.readField(myService, "fieldOfMyService", true);
System.out.println(
"fieldOfMyService via reflection after change:" + myService.getClass() + " " + fieldOfMyServiceAfter);
myService.printFieldOfMyService();
}
}
With @EnableCaching
这段代码会打印
fieldOfMyService:class rg.so.qn.MyService : false
fieldOfMyService via reflection before change:class rg.so.qn.MyService$$EnhancerBySpringCGLIB$$dfa75fca false
fieldOfMyService via reflection after change:class rg.so.qn.MyService$$EnhancerBySpringCGLIB$$dfa75fca true
fieldOfMyService:class rg.so.qn.MyService : false
这里通过反射更新的值是针对运行时子类实例的
Without @EnableCaching
这段代码会打印
fieldOfMyService:class rg.so.qn.MyService : false
fieldOfMyService via reflection before change:class rg.so.qn.MyService false
fieldOfMyService via reflection after change:class rg.so.qn.MyService true
fieldOfMyService:class rg.so.qn.MyService : true
这里通过反射更新的值是针对实际实例的。
Change 2:修改数据类型MyService.fieldOfMyService
to Boolean
@Service
public class MyService {
private Boolean fieldOfMyService = false;
public void printFieldOfMyService() {
System.out.println("fieldOfMyService:" + this.getClass()+" : "+fieldOfMyService);
}
@Cacheable("noOpMethod")
public void noOpMethod() {
}
}
With @EnableCaching
此代码将导致 NPEMyServiceTest.test()
, boolean fieldOfMyService = (boolean) FieldUtils.readField(myService, "fieldOfMyService", true);
因为运行时代理将该字段初始化为 null。
Without @EnableCaching
这段代码将按预期运行。
希望这可以帮助。
Note : @SpringBootTest https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/context/SpringBootTest.html元注释为@ExtendWith
and @RunWith
是可以避免的。
-------------------------------------------Update---------------------------------------
也想分享这个问题的答案“有解决办法吗?”
要么使用 setter 方法来更新字段的状态
Or
如果反射是唯一的选择,则获取实际实例并使用反射来更新字段。
以下代码假设通过以下方式启用缓存@EnableCaching
并且铸件无需任何检查即可工作。请进行必要的修改以确保万无一失。
@Test
public void test() throws Exception {
myService.printFieldOfMyService();
MyService actualInstance = (MyService)((Advised) myService).getTargetSource().getTarget();
boolean fieldOfMyService = (boolean) FieldUtils.readField(actualInstance, "fieldOfMyService", true);
System.out.println(
"fieldOfMyService via reflection before change:" + myService.getClass() + " " + fieldOfMyService);
FieldUtils.writeField(actualInstance, "fieldOfMyService", true, true);
boolean fieldOfMyServiceAfter = (boolean) FieldUtils.readField(actualInstance, "fieldOfMyService", true);
System.out.println(
"fieldOfMyService via reflection after change:" + myService.getClass() + " " + fieldOfMyServiceAfter);
myService.printFieldOfMyService();
}