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

2024-06-24 09:52

本文主要是介绍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代理方式变更,特此梳理一下。

如下代码结果展示

类代码


/*** @author YY-帆S* @Date 2024/4/16 21:16*/
@Service
@Slf4j
public class TestProxy {@Value("${test.boolean:true}")public boolean aBoolean;public void testAQuery() {log.info("testAQuery:{}", aBoolean);TestProxy bean = SpringContextUtils.getBean(TestProxy.class);bean.testBQuery();bean.testCQuery();}public void testBQuery() {log.info("testBQuery:{}", aBoolean);}private void testCQuery() {log.info("testCQuery:{}", aBoolean);}@Componentpublic class InnerClass {public void testInnerQuery() {log.info("testInnerQuery:{}", aBoolean);}}//第一次 不加上
//  @Async
//第二次加上@Asyncpublic void testAsync() {}
}

 调用代码

    @ResourceTestProxy testProxy;@ResourceTestProxy.InnerClass innerClass;@GetMapping("testQuery")public CommonResult<Object> testQuery() {testProxy.testAQuery();innerClass.testInnerQuery();return new CommonResult<>();}

不加上@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)减少直接读写属性,而是调用其中的方法

//类代码@Service
@Slf4j
@Data
public class TestProxy {@Value("${test.boolean.b:true}")public Boolean bBoolean;@Asyncpublic void testAsync() {}
}//……调用代码@ResourceTestProxy testProxy;@GetMapping("testQuery")public CommonResult<Object> testQuery() {log.info("user func:{}", testProxy.getBBoolean());log.info("direct use: {}", testProxy.bBoolean);return new CommonResult<>();}

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

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

二、Spring的代理模式

1、静态代理(Static Proxy)

1)原理

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

2)优缺点

优点

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

缺点

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

3)代码实现

public interface UserService {void addUser(String name);
}public class UserServiceImpl implements UserService {@Overridepublic void addUser(String name) {System.out.println("Adding user: " + name);}
}public class UserServiceProxy implements UserService {private UserServiceImpl userService;public UserServiceProxy(UserServiceImpl userService) {this.userService = userService;}@Overridepublic void addUser(String name) {System.out.println("Before adding user");userService.addUser(name);System.out.println("After adding user");}
}// 使用代理
public class Main {public static void main(String[] args) {UserServiceImpl userService = new UserServiceImpl();UserServiceProxy proxy = new UserServiceProxy(userService);proxy.addUser("Alice");}
}

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

1)原理

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

2)优缺点

优点

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

缺点

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

3)代码实现

public interface UserService {void addUser(String name);
}public class UserServiceImpl implements UserService {@Overridepublic void addUser(String name) {System.out.println("Adding user: " + name);}
}import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;public class UserServiceProxy implements InvocationHandler {private Object target;public UserServiceProxy(Object target) {this.target = target;}public Object getProxy() {return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("Before method");Object result = method.invoke(target, args);System.out.println("After method");return result;}
}// 使用代理
UserService target = new UserServiceImpl();
UserService proxy = (UserService) new UserServiceProxy(target).getProxy();
proxy.addUser("Alice");

3、cglib 代理(Code Generation Library Proxy)

1)原理

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

2)优缺点

优点

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

缺点

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

3)代码实现

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;public class ProductService {public void addProduct(String name) {System.out.println("Adding product: " + name);}
}public class ProductServiceProxy implements MethodInterceptor {private Object target;public ProductServiceProxy(Object target) {this.target = target;}public Object getProxy() {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(target.getClass());enhancer.setCallback(this);return enhancer.create();}@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("Before method");Object result = proxy.invokeSuper(obj, args);System.out.println("After method");return result;}
}// 使用代理
ProductService target = new ProductService();
ProductService proxy = (ProductService) new ProductServiceProxy(target).getProxy();
proxy.addProduct("Laptop");

总结:

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

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

这篇关于Spring 内部类获取不到@Value配置值问题排查(附Spring代理方式)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1089817

相关文章

linux生产者,消费者问题

pthread_cond_wait() :用于阻塞当前线程,等待别的线程使用pthread_cond_signal()或pthread_cond_broadcast来唤醒它。 pthread_cond_wait() 必须与pthread_mutex 配套使用。pthread_cond_wait()函数一进入wait状态就会自动release mutex。当其他线程通过pthread

如何突破底层思维方式的牢笼

我始终认为,牛人和普通人的根本区别在于思维方式的不同,而非知识多少、阅历多少。 在这个世界上总有一帮神一样的人物存在。就像读到的那句话:“人类就像是一条历史长河中的鱼,只有某几条鱼跳出河面,看到世界的法则,但是却无法改变,当那几条鱼中有跳上岸,进化了,改变河道流向,那样才能改变法则。”  最近一段时间一直在不断寻在内心的东西,同时也在不断的去反省和否定自己的一些思维模式,尝试重

问题:第一次世界大战的起止时间是 #其他#学习方法#微信

问题:第一次世界大战的起止时间是 A.1913 ~1918 年 B.1913 ~1918 年 C.1914 ~1918 年 D.1914 ~1919 年 参考答案如图所示

Java五子棋之坐标校正

上篇针对了Java项目中的解构思维,在这篇内容中我们不妨从整体项目中拆解拿出一个非常重要的五子棋逻辑实现:坐标校正,我们如何使漫无目的鼠标点击变得有序化和可控化呢? 目录 一、从鼠标监听到获取坐标 1.MouseListener和MouseAdapter 2.mousePressed方法 二、坐标校正的具体实现方法 1.关于fillOval方法 2.坐标获取 3.坐标转换 4.坐

Spring Cloud:构建分布式系统的利器

引言 在当今的云计算和微服务架构时代,构建高效、可靠的分布式系统成为软件开发的重要任务。Spring Cloud 提供了一套完整的解决方案,帮助开发者快速构建分布式系统中的一些常见模式(例如配置管理、服务发现、断路器等)。本文将探讨 Spring Cloud 的定义、核心组件、应用场景以及未来的发展趋势。 什么是 Spring Cloud Spring Cloud 是一个基于 Spring

Linux 安装、配置Tomcat 的HTTPS

Linux 安装 、配置Tomcat的HTTPS 安装Tomcat 这里选择的是 tomcat 10.X ,需要Java 11及更高版本 Binary Distributions ->Core->选择 tar.gz包 下载、上传到内网服务器 /opt 目录tar -xzf 解压将解压的根目录改名为 tomat-10 并移动到 /opt 下, 形成个人习惯的路径 /opt/tomcat-10

Javascript高级程序设计(第四版)--学习记录之变量、内存

原始值与引用值 原始值:简单的数据即基础数据类型,按值访问。 引用值:由多个值构成的对象即复杂数据类型,按引用访问。 动态属性 对于引用值而言,可以随时添加、修改和删除其属性和方法。 let person = new Object();person.name = 'Jason';person.age = 42;console.log(person.name,person.age);//'J

java8的新特性之一(Java Lambda表达式)

1:Java8的新特性 Lambda 表达式: 允许以更简洁的方式表示匿名函数(或称为闭包)。可以将Lambda表达式作为参数传递给方法或赋值给函数式接口类型的变量。 Stream API: 提供了一种处理集合数据的流式处理方式,支持函数式编程风格。 允许以声明性方式处理数据集合(如List、Set等)。提供了一系列操作,如map、filter、reduce等,以支持复杂的查询和转

uniapp接入微信小程序原生代码配置方案(优化版)

uniapp项目需要把微信小程序原生语法的功能代码嵌套过来,无需把原生代码转换为uniapp,可以配置拷贝的方式集成过来 1、拷贝代码包到src目录 2、vue.config.js中配置原生代码包直接拷贝到编译目录中 3、pages.json中配置分包目录,原生入口组件的路径 4、manifest.json中配置分包,使用原生组件 5、需要把原生代码包里的页面修改成组件的方

2024.6.24 IDEA中文乱码问题(服务器 控制台 TOMcat)实测已解决

1.问题产生原因: 1.文件编码不一致:如果文件的编码方式与IDEA设置的编码方式不一致,就会产生乱码。确保文件和IDEA使用相同的编码,通常是UTF-8。2.IDEA设置问题:检查IDEA的全局编码设置和项目编码设置是否正确。3.终端或控制台编码问题:如果你在终端或控制台看到乱码,可能是终端的编码设置问题。确保终端使用的是支持你的文件的编码方式。 2.解决方案: 1.File -> S