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

    @Component
    public class InnerClass {
        public void testInnerQuery() {
            log.info("testInnerQuery:{}", aBoolean);
        }
    }

//第一次 不加上
//  @Async
//第二次加上
    @Async
    public void testAsync() {
    }
}

 调用代码

    @Resource
    TestProxy testProxy;

    @Resource
    TestProxy.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;

    @Async
    public void testAsync() {
    }
}

//……调用代码
    @Resource
    TestProxy 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 {
    @Override
    public 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;
    }

    @Override
    public 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 {
    @Override
    public 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);
    }

    @Override
    public 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();
    }

    @Override
    public 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代理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/737547.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

用于射频功率应用的氮化铝电阻元件

EAK推出了新的厚膜氮化铝 &#xff08;AlN&#xff09; 电阻器和端接系列&#xff0c;以补充公司现有的产品。传统上&#xff0c;射频功率电阻元件采用氧化铍&#xff08;BeO&#xff09;陶瓷材料作为陶瓷基板;然而&#xff0c;由于国际上要求从产品中去除BeO的压力&#xff0c…

第T2周:彩色图片分类

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 &#x1f449; 要求&#xff1a; 学习如何编写一个完整的深度学习程序了解分类彩色图片会灰度图片有什么区别测试集accuracy到达72% &#x1f9be;我的环境&am…

【ajax核心05】宏任务与微任务

ES6之后引入Promise对象(用来管理异步任务)&#xff0c;让JS引擎也可以发起异步任务 一&#xff1a;异步任务分类 异步任务分为&#xff1a;宏任务与微任务 宏任务 由浏览器环境执行的异步代码 具体宏任务分类 微任务 由JS引擎执行的代码 创建Promise对象时&#xff0c;…

数据清洗!即插即用!异常值、缺失值、离群值处理、残差分析和孤立森林异常检测,确保数据清洗的全面性和准确性,MATLAB程序!

适用平台&#xff1a;Matlab2021版及以上 数据清洗是数据处理和分析中的一个关键步骤&#xff0c;特别是对于像风电场这样的大型、复杂数据集。清洗数据的目的是为了确保数据的准确性、一致性和完整性&#xff0c;从而提高数据分析的质量和可信度&#xff0c;是深度学习训练和…

STM32单片机系统

1.STM32最小系统 微型计算机&#xff08;面&#xff09; 单片机最小系统是指能够将单片机芯片运行所必需的最少的硬件电路集成在一起的系统。 它是一种基本的单片机应用系统&#xff0c;通常由主芯片&#xff0c;时钟电路&#xff0c;复位电路&#xff0c;电源电路&#xff0c…

407串口01发送

实验一&#xff1a; 工程。 链接&#xff1a;https://pan.baidu.com/s/1g8DV4yZWOix0BbcZ08LYDQ?pwd2176 提取码&#xff1a;2176串口1的使用。发送功能。 单片机发送信息到电脑。 通过串口进行通信。 首先单片机这边。 单片机这边&#xff0c;需要对单片机的串口模块进行使…

小车启动底盘功能包

传感器与小车底盘的集成 新建功能包 catkin_create_pkg mycar_start roscpp rospy std_msgs ros_arduino_python usb_cam ydlidar_ros_driver功能包下创建launch文件夹&#xff0c;launch文件夹中新建launch文件&#xff0c;文件名start.launch。 内容如下 <!-- 机器人启动…

雷达标定与解析

融合雷达与解析雷达数据的相关代码。感谢开源社区的贡献。以下代码继承了很多人的工作。 如果是单雷达&#xff1a; 直接进行标定&#xff0c;所以就是接收相关的话题然后发布。 lidar_calibration_params.yaml&#xff1a; calibration:在这个接口里面x_offset: 0.0y_offset:…

免费内网穿透工具 ,快解析内网穿透解决方案

在IPv4公网IP严重不足的环境下&#xff0c;内网穿透技术越来越多的被人们所使用&#xff0c;使用内网穿透技术的好处有很多。 1&#xff1a;无需公网ip 物以稀为贵&#xff0c;由于可用的公网IP地址越来越少&#xff0c;价格也是水涨船高&#xff0c;一个固定公网IP一年的成本…

想让Python序列切片更高效?这些技巧你不可不知!

目录 1、自定义类实现切片 🍏 1.1 实现__getitem__方法 1.2 支持正负索引与步长 2、利用 collections.abc 模块 🧠 2.1 继承MutableSequence类 2.2 重写关键方法 3、使用标准库itertools.slice 🍲 3.1 itertools工具介绍 3.2 slice函数应用实例 4、通过生成器实…

Docker Compose--安装Nginx--方法/实例

原文网址&#xff1a;Docker Compose--安装Nginx--方法/实例_IT利刃出鞘的博客-CSDN博客 简介 说明 本文介绍Docker Compose如何安装Nginx。 目录结构 ├── config │ ├── cert │ │ ├── xxx_bundle.pem │ │ └── xxx.key │ ├── conf.d │ …

APP客户端接口本地缓存,降低请求量和请求峰值,减少云资源成本

背景 静态信息&#xff1a;非实时有状态的数据 针对资源位、评价等静态信息在xx点高峰时进行缓存&#xff0c;达到降低请求量和请求峰值的目标。 在成本预算控制下&#xff0c;云资源成本和WAF都受限于请求峰值。 出于业务和数据安全考虑&#xff0c;公司希望接入阿里云的WAF&a…

头歌——机器、深度学习——手写体识别

第1关&#xff1a;神经网络基本概念 任务描述 本关任务&#xff1a;根据本节课所学知识完成本关所设置的选择题。 相关知识 为了完成本关任务&#xff0c;你需要掌握&#xff1a;1.神经网络基本概念。 神经网络基本概念 神经网络由输入层、隐藏层、输出层组成&#xff1b;…

安卓逆向经典案例—H5appXX运维

H5app的class不一定是android.webkit.WebView 也可能是腾讯X5内核或者是uc webview 殊途同归也要去hook webview的系统函数和可调式方法setWebContentsDebuggingEnabled。突破sign算法&#xff0c;输出协议和加密算法的作用是什么&#xff1f;分析c-sign值 在加密的位置下断点 …

爱心商城管理系统的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;管理员管理&#xff0c;企业管理&#xff0c;用户管理&#xff0c;论坛管理&#xff0c;商品管理&#xff0c;公告管理&#xff0c;用户捐赠 企业账户功能包括&#xff1a;系统首页&#xff0c;个人中…

2009年-2022年 地级市-环境污染处罚数据

环境污染处罚数据是环境保护领域中重要的信息资源&#xff0c;它记录了因违反环保法律法规而受到行政处罚或法律制裁的具体情况。这些数据对于提高公众的环保意识、促进企业采取环保措施以及推动环境治理具有重要作用。 数据内容概述 违法行为的主体&#xff1a;即受到处罚的…

【树形dp 换根法 BFS】2581. 统计可能的树根数目

本文涉及知识点 CBFS算法 动态规划汇总 图论知识汇总 树形dp 换根法 BFS LeetCode 2581. 统计可能的树根数目 Alice 有一棵 n 个节点的树&#xff0c;节点编号为 0 到 n - 1 。树用一个长度为 n - 1 的二维整数数组 edges 表示&#xff0c;其中 edges[i] [ai, bi] &#xf…

LoRaWAN在嵌入式网络通信中的应用:打造高效远程监控系统(附代码示例)

引言 随着物联网&#xff08;IoT&#xff09;技术的发展&#xff0c;远程监控系统在各个领域的应用越来越广泛。LoRaWAN&#xff08;Long Range Wide Area Network&#xff09;作为一种低功耗广域网通信协议&#xff0c;因其长距离传输、低功耗和高可靠性等特点&#xff0c;成为…

Apollo9.0 PNC源码学习之Planning模块(二)—— planning_component

前面文章: Apollo9.0 PNC源码学习之Planning模块(一)—— 规划概览 0 Planning代码框架速览 1 planning_component源码解析 modules/planning/planning_component/planning_component.h #pragma once#include <memory>#

在vue项目中集成cesium

首先创建一个新的vue项目 安装vite中cesium插件 https://github.com/nshen/vite-plugin-cesium 安装插件 npm i cesium vite-plugin-cesium vite -D配置插件 注释原有样式 修改代码 效果