Java Spring缓存是否会影响反射?

3

我最近在使用Spring Boot和缓存。在我的测试中,我有时会使用反射技术。

下面是一个例子:

@Service
public class MyService {

    private boolean fieldOfMyService = false;

    public void printFieldOfMyService() {
        System.out.println("fieldOfMyService:" + fieldOfMyService);
    }

    @Cacheable("noOpMethod")
    public void noOpMethod() {
    }

}

以下是测试内容:

@RunWith(SpringRunner.class)
@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:" + fieldOfMyService);

        FieldUtils.writeField(myService, "fieldOfMyService", true, true);

        boolean fieldOfMyServiceAfter = (boolean) FieldUtils.readField(myService, "fieldOfMyService", true);

        System.out.println("fieldOfMyService via reflection after change:" + fieldOfMyServiceAfter);

        myService.printFieldOfMyService();

    }

}

正如您所看到的,它非常简单:

  • MyService 有一个私有字段fieldOfMyService
  • 测试通过反射将其从false更改为true

问题

  • 没有缓存时一切都运行良好.. 这是输出结果:
fieldOfMyService:false
fieldOfMyService via reflection before change:false
fieldOfMyService via reflection after change:true
fieldOfMyService:true

我现在通过以下方式激活缓存:

  • 在Spring中使用@EnableCaching
  • 随后你会得到如下结果:
fieldOfMyService:false
fieldOfMyService via reflection before change:false
fieldOfMyService via reflection after change:true
fieldOfMyService:false                      <<<<< !!!

简而言之:

  • 当缓存被激活时,通过反射所做的更改似乎对该服务没有影响。

有趣的是,只有当相关服务实际上使用至少一个@Caching注释方法进行缓存时,才会发生这种情况。如果该服务没有这个注释,就不会出现这种情况:

@Service
public class MyService {

    private boolean fieldOfMyService = false;

    public void printFieldOfMyService() {
        System.out.println("fieldOfMyService:" + fieldOfMyService);
    }

}

......那它仍然可以工作。

我猜这与启用缓存时添加的层有关。但是为什么呢?是否有解决办法?

先感谢您的帮助 :-)


您需要更多的澄清来作为答案吗? - R.G
1个回答

1
行为上的差异是由Spring框架进行的代理造成的。当需要对bean进行特殊处理时,例如缓存,在运行时会创建一个bean的代理。Spring有两种代理技术:基于JDK和CGLIB的代理。请阅读共享的文档链接以获取更多详细信息。
在共享的示例代码中,使用了CGLIB代理(提示:MyService没有实现接口)。CGLIB创建的运行时子类将具有原始类的所有字段,但基于数据类型将保持为null /默认值(这里是false)。代理的方法调用将委托给原始实例方法。
对代码进行以下更改将提供有关如何工作的更多详细信息。 更改1:同时打印object.getClass()
@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();

    }

}

使用@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

这里通过反射更新的值是运行时子类实例的值。
如果没有使用@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

这里通过反射更新的值是实际实例的值。 更改2:修改MyService.fieldOfMyService的数据类型为Boolean
@Service
public class MyService {

    private Boolean fieldOfMyService = false;

    public void printFieldOfMyService() {
        System.out.println("fieldOfMyService:" + this.getClass()+" : "+fieldOfMyService);
    }

    @Cacheable("noOpMethod")
    public void noOpMethod() {
    }

}

使用@EnableCaching,这段代码将在MyServiceTest.test()处导致NPE,boolean fieldOfMyService = (boolean) FieldUtils.readField(myService, "fieldOfMyService", true);,因为运行时代理将该字段初始化为null。

如果没有@EnableCaching,则此代码将按预期运行。

希望这可以帮助您。

注意:@SpringBootTest是元注释@ExtendWith@RunWith可以避免使用。

------------------------------- 更新---------------------------------------

想要分享对这个问题的答案“是否有解决方案?”

要么使用setter方法来更新字段状态

要么

如果反射是唯一的选择,则获取实际实例并使用反射更新字段。

以下代码假定通过@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();

}

是的!@R.G非常感谢您 - 这澄清了我一直怀疑的问题,有了您的更新,我甚至可以在测试中激活缓存。再次感谢 - 非常感激。 - daravetu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接