当前位置:   article > 正文

Spring 内部类获取不到@Value配置值问题排查(附Spring代理方式)

Spring 内部类获取不到@Value配置值问题排查(附Spring代理方式)

目录

一、实例问题

1、现象

2、原因

3、解决

二、Spring的代理模式

1、静态代理(Static Proxy)

1)原理

2)优缺点

3)代码实现

2、JDK动态代理(JDK Dynamic Proxy)

1)原理

2)优缺点

3)代码实现

3、cglib 代理(Code Generation Library Proxy)

1)原理

2)优缺点

3)代码实现


一、实例问题

1、现象

业务场景中出现过使用内部类获取属性的时候获取不到,导致业务逻辑执行异常,观察代码后发现原因是最底层有@Async的方法导致Spring代理方式变更,特此梳理一下。

如下代码结果展示

类代码

  1. /**
  2. * @author YY-帆S
  3. * @Date 2024/4/16 21:16
  4. */
  5. @Service
  6. @Slf4j
  7. public class TestProxy {
  8. @Value("${test.boolean:true}")
  9. public boolean aBoolean;
  10. public void testAQuery() {
  11. log.info("testAQuery:{}", aBoolean);
  12. TestProxy bean = SpringContextUtils.getBean(TestProxy.class);
  13. bean.testBQuery();
  14. bean.testCQuery();
  15. }
  16. public void testBQuery() {
  17. log.info("testBQuery:{}", aBoolean);
  18. }
  19. private void testCQuery() {
  20. log.info("testCQuery:{}", aBoolean);
  21. }
  22. @Component
  23. public class InnerClass {
  24. public void testInnerQuery() {
  25. log.info("testInnerQuery:{}", aBoolean);
  26. }
  27. }
  28. //第一次 不加上
  29. // @Async
  30. //第二次加上
  31. @Async
  32. public void testAsync() {
  33. }
  34. }

 调用代码

  1. @Resource
  2. TestProxy testProxy;
  3. @Resource
  4. TestProxy.InnerClass innerClass;
  5. @GetMapping("testQuery")
  6. public CommonResult<Object> testQuery() {
  7. testProxy.testAQuery();
  8. innerClass.testInnerQuery();
  9. return new CommonResult<>();
  10. }

不加上@Asnyc的结果

加上@Async的结果

2、原因

加Async之前的bean属性,内部aBoolean=true

加Async之后的bean,内部aBoolean=false,(默认值)

可以观察到,加@Async或@Transcational 这类的注解时,会导致spring切换对类切换代理方式,为cglib的代理模式,翻了spring源码的话会发现,spring生成代理对象的时候使用了Objenesis来创建,Objenesis可以绕过构造方法以及相关的初始化来创建对象,所以生成的代理类中所有的属性全部都是空的。

参考文档:

 spring——事务动态代理造成属性为null_切面生成的cglb代理类里面的属性为null-CSDN博客

3、解决

1)减少直接读写属性,而是调用其中的方法

  1. //类代码
  2. @Service
  3. @Slf4j
  4. @Data
  5. public class TestProxy {
  6. @Value("${test.boolean.b:true}")
  7. public Boolean bBoolean;
  8. @Async
  9. public void testAsync() {
  10. }
  11. }
  12. //……调用代码
  13. @Resource
  14. TestProxy testProxy;
  15. @GetMapping("testQuery")
  16. public CommonResult<Object> testQuery() {
  17. log.info("user func:{}", testProxy.getBBoolean());
  18. log.info("direct use: {}", testProxy.bBoolean);
  19. return new CommonResult<>();
  20. }

结果:使用方法获取属性则正常返回配置后的结果true,直接使用属性的为null 空

2)多自测,兄弟们,必现的case,测一下就知道了

二、Spring的代理模式

1、静态代理(Static Proxy)

1)原理

静态代理在编译时确定代理类,代理类和目标类都实现相同的接口。代理类在调用目标类的方法之前或之后添加一些额外的操作。

2)优缺点

优点

  • 简单直观:实现简单,易于理解和调试。
  • 编译时检查:代理类在编译时确定,可以进行类型检查。

缺点

  • 代码冗余:每个接口需要对应一个代理类,导致代码量增加。
  • 不灵活:代理逻辑在编译时确定,无法在运行时动态改变。
  • 维护困难:如果接口方法增加或修改,代理类需要同步修改。

3)代码实现

  1. public interface UserService {
  2. void addUser(String name);
  3. }
  4. public class UserServiceImpl implements UserService {
  5. @Override
  6. public void addUser(String name) {
  7. System.out.println("Adding user: " + name);
  8. }
  9. }
  10. public class UserServiceProxy implements UserService {
  11. private UserServiceImpl userService;
  12. public UserServiceProxy(UserServiceImpl userService) {
  13. this.userService = userService;
  14. }
  15. @Override
  16. public void addUser(String name) {
  17. System.out.println("Before adding user");
  18. userService.addUser(name);
  19. System.out.println("After adding user");
  20. }
  21. }
  22. // 使用代理
  23. public class Main {
  24. public static void main(String[] args) {
  25. UserServiceImpl userService = new UserServiceImpl();
  26. UserServiceProxy proxy = new UserServiceProxy(userService);
  27. proxy.addUser("Alice");
  28. }
  29. }

2、JDK动态代理(JDK Dynamic Proxy)

1)原理

JDK动态代理基于Java的反射机制,通过在运行时生成代理类来实现。代理类必须实现一个或多个接口。JDK动态代理使用java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口来创建代理实例。

2)优缺点

优点

  • 接口支持:适用于代理实现了接口的类。
  • 轻量级:相比于CGLIB,JDK动态代理生成的代理类较轻量。
  • 无需依赖第三方库:基于JDK自带的反射机制实现。

缺点

  • 只能代理接口:目标类必须实现接口,如果没有接口则无法使用。
  • 性能较低:反射机制相对较慢,性能不如CGLIB。

3)代码实现

  1. public interface UserService {
  2. void addUser(String name);
  3. }
  4. public class UserServiceImpl implements UserService {
  5. @Override
  6. public void addUser(String name) {
  7. System.out.println("Adding user: " + name);
  8. }
  9. }
  10. import java.lang.reflect.InvocationHandler;
  11. import java.lang.reflect.Method;
  12. import java.lang.reflect.Proxy;
  13. public class UserServiceProxy implements InvocationHandler {
  14. private Object target;
  15. public UserServiceProxy(Object target) {
  16. this.target = target;
  17. }
  18. public Object getProxy() {
  19. return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
  20. }
  21. @Override
  22. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  23. System.out.println("Before method");
  24. Object result = method.invoke(target, args);
  25. System.out.println("After method");
  26. return result;
  27. }
  28. }
  29. // 使用代理
  30. UserService target = new UserServiceImpl();
  31. UserService proxy = (UserService) new UserServiceProxy(target).getProxy();
  32. proxy.addUser("Alice");

3、cglib 代理(Code Generation Library Proxy)

1)原理

CGLIB代理通过生成目标类的子类并覆盖其方法来实现代理。它使用字节码增强技术。

2)优缺点

优点

  • 无需接口:可以代理没有实现接口的类。
  • 性能较高:通常情况下,CGLIB代理的性能比JDK动态代理高。

缺点

  • 依赖第三方库:需要CGLIB库(Spring中已经包含)。
  • 无法代理final类和方法:由于CGLIB通过生成子类来实现代理,final类和方法无法被代理。
  • 内存开销大:生成子类会占用更多内存

3)代码实现

  1. import net.sf.cglib.proxy.Enhancer;
  2. import net.sf.cglib.proxy.MethodInterceptor;
  3. import net.sf.cglib.proxy.MethodProxy;
  4. import java.lang.reflect.Method;
  5. public class ProductService {
  6. public void addProduct(String name) {
  7. System.out.println("Adding product: " + name);
  8. }
  9. }
  10. public class ProductServiceProxy implements MethodInterceptor {
  11. private Object target;
  12. public ProductServiceProxy(Object target) {
  13. this.target = target;
  14. }
  15. public Object getProxy() {
  16. Enhancer enhancer = new Enhancer();
  17. enhancer.setSuperclass(target.getClass());
  18. enhancer.setCallback(this);
  19. return enhancer.create();
  20. }
  21. @Override
  22. public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
  23. System.out.println("Before method");
  24. Object result = proxy.invokeSuper(obj, args);
  25. System.out.println("After method");
  26. return result;
  27. }
  28. }
  29. // 使用代理
  30. ProductService target = new ProductService();
  31. ProductService proxy = (ProductService) new ProductServiceProxy(target).getProxy();
  32. proxy.addProduct("Laptop");

总结:

  • JDK动态代理:适用于目标类实现接口的情况,轻量但性能较低,无法代理没有实现接口的类。
  • CGLIB代理:适用于没有实现接口的类,性能较高但依赖第三方库,无法代理final类和方法。
  • 静态代理:实现简单但不灵活,代码冗余,适用于代理逻辑固定的情况。

根据具体需求和应用场景,可以选择合适的代理模式。Spring默认选择JDK动态代理,如果目标类没有实现接口,则使用CGLIB代理。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/一键难忘520/article/detail/769212
推荐阅读
相关标签
  

闽ICP备14008679号