Различие в поведении связано с проксированием, выполняемым средой Spring. Когда для bean-компонента требуется какая-либо специальная обработка, в данном случае Caching, во время выполнения создается прокси для bean-компонента. В Spring есть два типа методов прокси - JDK- и CGLIB-прокси . Пожалуйста, прочитайте ссылку на документацию для получения более подробной информации.
В приведенном примере кода используется прокси-сервер 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
этот код приведет к NPE в MyServiceTest.test()
, boolean fieldOfMyService = (boolean) FieldUtils.readField(myService, "fieldOfMyService", true);
, поскольку прокси времени выполнения имеет поле, инициализированное нулевым значением.
Без @EnableCaching
этот код будет работать как ожидалось.
Надеюсь, это поможет.
Примечание: @ SpringBootTest мета-аннотируется с помощью @ExtendWith
и @RunWith
можно избежать.
------------ ------------------- Обновление ------------------------ ---------------
Мысль о том, чтобы поделиться ответом и на этот вопрос: «И есть ли решение для него?»
Либо использовать сеттер методы для обновления состояния поля
Или
Если отражение - единственный вариант, получите фактический экземпляр и используйте отражение для обновления поля.
В следующем коде предполагается, что кэширование включено через @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();
}