【问题标题】:Does java spring caching break reflection?java spring缓存会破坏反射吗?
【发布时间】:2020-04-23 23:48:45
【问题描述】:

我最近正在使用 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

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

  • @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);
    }

}

.. 然后它仍然有效。

我猜这与激活缓存时添加的层有关。但为什么?有没有解决办法?

提前感谢您的帮助:-)

【问题讨论】:

  • 您还需要澄清吗?

标签: java spring caching reflection


【解决方案1】:

行为上的差异是由于 Spring 框架完成的代理。当 bean 需要任何特殊处理时,在本例中为 Caching,在运行时创建 bean 的代理。 Spring 中有两种类型的代理技术 - JDK- and CGLIB-based proxies 。请阅读共享的文档链接以了解更多详细信息。

在共享的示例代码中,发挥作用的是 CGLIB 代理(线索:MyService 没有实现接口)。 CGLIB 创建的运行时子类将具有原始类的所有字段,但根据数据类型(此处为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()boolean fieldOfMyService = (boolean) FieldUtils.readField(myService, "fieldOfMyService", true); 出现NPE,因为运行时代理将字段初始化为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 非常感谢您-这澄清了我的怀疑,并且通过您的更新,我什至可以激活测试中的缓存。再次感谢 - 非常感谢。
猜你喜欢
  • 2013-05-14
  • 1970-01-01
  • 2012-03-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-19
相关资源
最近更新 更多