unity-调用讯飞星火语音唤醒-新版windowsSDK

2024-06-22 05:28

本文主要是介绍unity-调用讯飞星火语音唤醒-新版windowsSDK,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

调用讯飞星火语音唤醒-新版windowsSDK

先贴一张在unity中 wins系统下成功调用新版的讯飞windowsSDK的运行截图

调用成功为什么要用讯飞的语音唤醒?
项目中需要在unity和win系统下进行语音唤醒开启语音对话,而语音唤醒比较成熟的方案大多都是在linux系统下的,如snowboy,还有就是安卓系统的语音唤醒(各大厂都有)。win系统的就很少,我了解到的也只有讯飞有在做win系统的语音唤醒,如果从linux和安卓将语音唤醒功能移植到win下,不知道要花费多少时间。因此选择了讯飞

为什么要用这个新版的windowsSDK?

  1. 新版便宜。在官方费用上可以看到,旧版的低消为8000(最近618活动打折到了5600);而新版的低消为2500,打折后为1750
  2. 唤醒词设置方便。旧版需要在讯飞的官网上设置唤醒词,然后下载整个sdk,然后将sdk中的wakeupresource.jet文件替换,才能完成唤醒词的修改,并且如果你的语音唤醒过期了她还会提示你无法下载…;新版的sdk经过测试,将唤醒词以txt文本的方式写在了本地,也就意味着你直接在本地修改txt文件就可以完成唤醒词的修改,极大的减轻了开发人员的额外工作
  3. 与时俱进,新版肯定较旧版而言更有优势才会出新版

然而旧版的语音唤醒也还是有它的优势的

  1. 网上已经有很多调用旧版sdk的教程,跟着写就可以实现windowsSDK语音唤醒(是的我就是其中之一),无需将时间耗费在亲自写代码去调用dll中提供的api等各种繁琐而且有难度的事情上。官方的windowsSDK是dll的形式,如果不懂c++以及C#调用dll的代码的话,自己写会十分痛苦和消耗时间;新版的由于推出较晚,使用的人不多,网上相关的教程更是基本没有,问客服也是一问三不知,我手撸了差不多一个星期才实现了基本的调用。
  2. 旧版的语音唤醒和语音识别是连在一起的,唤醒搞定之后识别和合成等的的调用流程都是一样的,不需要太多额外的工作
  3. 新版的sdk中在c++环境下调用是否快速,但在dll调用中则困难重重

大家可以权衡利弊,选择合适自己项目的语音唤醒(讯飞原版及新版)

在讯飞官网中下载新版的源码后,可以在visual studio中试运行,看看体验如何,官方的demo中是提供了读取本地音频进行唤醒识别和实时通过麦克风接收到的数据进行唤醒识别两个选项

时间关系,先直接贴上调用成功的代码

public class MSCDLL : MonoBehaviour
{private const string _ability = "e867a88f2"; //这是默认的,请不要修改private const string _appID = "在官网上申请的应用的appid";private const string _apiSecret = "秘钥";private const string _apiKey = "key";private const string TAG = "MSCDLL";int _dll_result = 0;IntPtr _akikt_handle = IntPtr.Zero;// Start is called before the first frame updatevoid Start(){byte[] bytes = ReadWavFile($"{Application.streamingAssetsPath}/output.wav");wakeup(bytes);//测试直接读取本地的音频,将其直接传给唤醒的api进行唤醒识别   }public void wakeup(byte[] bytes){//卸载&释放语音唤醒sdk资源_dll_result = AIKIT_UnInit();Debug.Log($"{TAG}>>>AIKIT_UnInit>>>{(_dll_result == 0 ? "success" : "fail")}");//初始化并设置语音唤醒参数AIKIT_InitParam param = new AIKIT_InitParam();param.AppID = _appID;param.ApiSecret = _apiSecret;param.ApiKey = _apiKey;param.AuthType = 0;_dll_result = AIKIT_Init(param);Debug.Log($"{TAG}>>>AIKIT_InitParam>>>{(_dll_result == 0 ? "success" : "fail")}");//注册回调AIKIT_OnOutput onOutput = new AIKIT_OnOutput(OnOutput);AIKIT_OnEventDelegate onEvent = new AIKIT_OnEventDelegate(OnEvent);AIKIT_OnErrorDelegate onError = new AIKIT_OnErrorDelegate(OnError);AIKIT_Callbacks callbacks = new AIKIT_Callbacks{outputCB = onOutput,eventCB = onEvent,errorCB = onError};_dll_result = AIKIT_RegisterAbilityCallback(_ability, callbacks);Debug.Log($"{TAG}>>>AIKIT_RegisterAbilityCallback>>>{(_dll_result == 0 ? "success" : "fail")}");//初始化语音唤醒引擎AIKIT_BaseParam engintParam = null;int resutl2 = AIKIT_EngineInit(_ability, engintParam);Debug.Log($"{TAG}>>>AIKIT_EngineInit>>>{(_dll_result == 0 ? "success" : "fail")}");//加载唤醒所需要的资源参数>>>唤醒词文件路径string value = (Application.streamingAssetsPath + "\\xbxb.txt").Replace("/", "\\").Replace("\\", "\\\\");IntPtr tem = Marshal.StringToHGlobalAnsi(value);AIKIT_CustomData loadData = new AIKIT_CustomData("key_word", tem, 2);_dll_result = AIKIT_LoadData(_ability, loadData);Debug.Log($"{TAG}>>>AIKIT_LoadData>>>{(_dll_result == 0 ? "success" : "fail")}");//指定要使用的唤醒词文件数据集合,每次会话开启前需要调用。_dll_result = AIKIT_SpecifyDataSet(_ability, "key_word", new int[] { 0 }, 1);Debug.Log($"{TAG}>>>AIKIT_SpecifyDataSet>>>{(_dll_result == 0 ? "success" : "fail")}");//设置唤醒词文件的参数IntPtr aikit_handle_ptr = AIKITBuilder_Create(BuilderType.BUILDER_TYPE_PARAM);AIKITBuilderHandle aikit_param_handle = (AIKITBuilderHandle)Marshal.PtrToStructure(aikit_handle_ptr, typeof(AIKITBuilderHandle));_dll_result = AIKITBuilder_AddBool(aikit_param_handle, "gramLoad", true);//这一个字段不添加会报错无法运行//result = AIKITBuilder_AddString(aikit_handle, "wdec_param_nCmThreshold", "0 0:999", "0 0:999".Length);//这个可以不添加IntPtr param_ptr = AIKITBuilder_BuildParam(aikit_param_handle);AIKIT_BaseParam aikit_param = (AIKIT_BaseParam)Marshal.PtrToStructure(param_ptr, typeof(AIKIT_BaseParam));Debug.Log($"{TAG}>>>AIKITBuilder_BuildParam>>>{(_dll_result == 0 ? "success" : "fail")}");//开启唤醒>>>同时返回句柄,用于给>>>AIKIT_WRITE>>>当做参数调用,这个句柄包含了会话信息,如果不一致无法执行后面的write和end这两个函数_dll_result = AIKIT_Start(_ability, aikit_param, IntPtr.Zero, ref _akikt_handle);Debug.Log($"{TAG}>>>AIKIT_Start>>>{(_dll_result == 0 ? "success" : "fail")}>>>{_dll_result}");IntPtr aikit_data_handle_ptr = AIKITBuilder_Create(BuilderType.BUILDER_TYPE_DATA);AIKITBuilderHandle aikit_data_handle = (AIKITBuilderHandle)Marshal.PtrToStructure(aikit_data_handle_ptr, typeof(AIKITBuilderHandle));//将音频数据引到指定对象中,并传给dllBuilderData builderData = new BuilderData();builderData.type = (int)BuilderDataType.DATA_TYPE_AUDIO;builderData.name = "wav";builderData.status = 0;builderData.data = bytes;builderData.len = bytes.Length;_dll_result = AIKITBuilder_AddBuf(aikit_data_handle, builderData);Debug.Log($"{TAG}>>>AIKITBuilder_AddBuf>>>{(_dll_result == 0 ? "success" : "fail")}>>>{_dll_result}");//将数据对象传给dll后,调用dll的api获取根据数据对象生成的inputdata对象IntPtr data_ptr = AIKITBuilder_BuildData(aikit_data_handle);//将获取到的dataptr指针强转成我们需要的对象AIKIT_BaseData input_data = (AIKIT_BaseData)Marshal.PtrToStructure(data_ptr, typeof(AIKIT_BaseData));//将获取到的inputdata对象传入>>>AIKIT_Write>>>开始检测唤醒AIKIT_HANDLE aikit_handle = (AIKIT_HANDLE)Marshal.PtrToStructure(_akikt_handle, typeof(AIKIT_HANDLE));_dll_result = AIKIT_Write(aikit_handle, input_data);Debug.Log($"{TAG}>>>AIKIT_Write>>>{(_dll_result == 0 ? "success" : "fail")}>>>{_dll_result}");_dll_result = AIKIT_End(aikit_handle);Debug.Log($"{TAG}>>>AIKIT_End>>>{(_dll_result == 0 ? "success" : "fail")}>>>{_dll_result}");}// Update is called once per framevoid Update(){}/// <summary>/// 卸载释放sdk/// </summary>/// <returns></returns>[DllImport("AEE_lib", CallingConvention = CallingConvention.StdCall)]public static extern Int32 AIKIT_UnInit();/// <summary>/// 初始化sdk/// </summary>/// <param name="data"></param>/// <returns></returns>[DllImport("AEE_lib", CallingConvention = CallingConvention.StdCall)]public static extern Int32 AIKIT_Init(AIKIT_InitParam data);/// <summary>/// 初始化引擎/// </summary>/// <param name="appid"></param>/// <param name="param"></param>/// <returns></returns>[DllImport("AEE_lib")]public static extern Int32 AIKIT_EngineInit(string appid, AIKIT_BaseParam param);/// <summary>/// 卸载释放引擎/// </summary>/// <param name="appid"></param>/// <returns></returns>[DllImport("AEE_lib", CallingConvention = CallingConvention.StdCall)]public static extern Int32 AIKIT_EngineUnInit(string appid);/// <summary>/// 注册回调/// </summary>/// <param name="appid"></param>/// <param name="param"></param>/// <returns></returns>[DllImport("AEE_lib", CallingConvention = CallingConvention.StdCall)]public static extern Int32 AIKIT_RegisterAbilityCallback(string appid, AIKIT_Callbacks param);/// <summary>/// 设置唤醒的参数/// </summary>/// <param name="appid"></param>/// <param name="param"></param>/// <returns></returns>[DllImport("AEE_lib", CallingConvention = CallingConvention.StdCall)]public static extern Int32 AIKIT_LoadData(string appid, AIKIT_CustomData param);/// <summary>/// 设置唤醒词文件信息/// </summary>/// <param name="appid"></param>/// <param name="key"></param>/// <param name="index"></param>/// <param name="count"></param>/// <returns></returns>[DllImport("AEE_lib", CallingConvention = CallingConvention.StdCall)]public static extern Int32 AIKIT_SpecifyDataSet(string appid, string key, int[] index, int count);/// <summary>/// 添加AIKITBuilderHandle对象/// </summary>/// <param name="builderType"></param>/// <returns></returns>[DllImport("AEE_lib", CallingConvention = CallingConvention.StdCall)]public static extern IntPtr AIKITBuilder_Create(BuilderType builderType);/// <summary>/// 通过AIKITBuilderHandle对象设置bool类型的参数/// </summary>/// <param name="handle"></param>/// <param name="key"></param>/// <param name="value"></param>/// <returns></returns>[DllImport("AEE_lib", CallingConvention = CallingConvention.StdCall)]public static extern Int32 AIKITBuilder_AddBool(AIKITBuilderHandle handle, string key, bool value);/// <summary>/// 通过AIKITBuilderHandle对象设置string类型的参数/// </summary>/// <param name="handle"></param>/// <param name="key"></param>/// <param name="value"></param>/// <param name="len"></param>/// <returns></returns>[DllImport("AEE_lib", CallingConvention = CallingConvention.StdCall)]public static extern Int32 AIKITBuilder_AddString(AIKITBuilderHandle handle, string key, string value, int len);/// <summary>/// 通过AIKITBuilderHandle对象输入BuilderData对象,对象中记录字节数据/// </summary>/// <param name="handle"></param>/// <param name="builderData"></param>/// <returns></returns>[DllImport("AEE_lib", CallingConvention = CallingConvention.StdCall)]public static extern Int32 AIKITBuilder_AddBuf(AIKITBuilderHandle handle, BuilderData builderData);/// <summary>/// 通过AIKITBuilderHandle对象将刚才设置好的参数转成AIKIT_BaseParam对象/// </summary>/// <param name="handle"></param>/// <returns></returns>[DllImport("AEE_lib", CallingConvention = CallingConvention.StdCall)]public static extern IntPtr AIKITBuilder_BuildParam(AIKITBuilderHandle handle);/// <summary>/// 通过AIKITBuilderHandle对象将刚才设置好的参数转成>>>AIKITBuilder_BuildData>>>对象/// </summary>/// <param name="handle"></param>/// <returns></returns>[DllImport("AEE_lib", CallingConvention = CallingConvention.StdCall)]public static extern IntPtr AIKITBuilder_BuildData(AIKITBuilderHandle handle);/// <summary>/// 正式开启唤醒/// </summary>/// <param name="appid"></param>/// <param name="param"></param>/// <param name="userContext"></param>/// <param name="outHandle"></param>/// <returns></returns>[DllImport("AEE_lib", CallingConvention = CallingConvention.StdCall)]public static extern Int32 AIKIT_Start(string appid, AIKIT_BaseParam param, IntPtr userContext, ref IntPtr outHandle);/// <summary>/// 传入需要唤醒检测的数据文件/// </summary>/// <param name="appid"></param>/// <param name="param"></param>/// <param name="userContext"></param>/// <param name="outHandle"></param>/// <returns></returns>[DllImport("AEE_lib", CallingConvention = CallingConvention.StdCall)]public static extern Int32 AIKIT_Write(AIKIT_HANDLE handle, AIKIT_BaseData input);[DllImport("AEE_lib", CallingConvention = CallingConvention.StdCall)]public static extern Int32 AIKIT_End(AIKIT_HANDLE handle);public static byte[] ReadWavFile(string filePath){// 确保文件存在if (!File.Exists(filePath)){Debug.Log(filePath);throw new FileNotFoundException("文件未找到", filePath);}// 读取文件的所有字节byte[] fileBytes = File.ReadAllBytes(filePath);return fileBytes;}private void OnOutput(IntPtr handle, AIKIT_BaseDataList output){   // 处理输出回调//将Intprt转成aikitoutputdata,然后获取输出结果AIKIT_HANDLE output_handle = (AIKIT_HANDLE)Marshal.PtrToStructure(handle, typeof(AIKIT_HANDLE));Debug.Log(output_handle.abilityID);//AIKIT_BaseDataList input_data = (AIKIT_BaseDataList)Marshal.PtrToStructure(output, typeof(AIKIT_BaseDataList));Debug.Log("唤醒成功!");}private void OnEvent(IntPtr handle, int eventType, IntPtr eventValue){   // 处理事件回调// 处理输出回调Debug.Log(eventValue);}private void OnError(IntPtr handle, int err, IntPtr desc){   // 处理错误回调Debug.Log(desc);}
}
[StructLayout(LayoutKind.Sequential)]public class AIKIT_InitParam
{public int AuthType;         // 授权方式,0=设备级授权,1=应用级授权  public string AppID;         // 应用id  public string ApiKey;        // 应用key  public string ApiSecret;     // 应用secret  public string WorkDir;       // sdk工作目录,需可读可写权限  public string ResDir;        // 只读资源存放目录,需可读权限  public string LicenseFile;   // 离线激活方式的授权文件存放路径,为空时需联网进行首次在线激活  public string BatchID;       // 授权批次  public string UDID;          // 用户自定义设备标识  public string CfgFile;       // 配置文件路径,包括文件名  
}
[StructLayout(LayoutKind.Sequential)]public class AIKIT_BaseParam
{public IntPtr next; // 链表指针,使用IntPtr代替void*指针public IntPtr key; // 指针,使用IntPtr代替const char*public IntPtr value; // 指针,使用IntPtr代替void*public IntPtr reserved; // 预留字段,使用IntPtr代替void*public int len; // 数据长度public int type; // 变量类型
}
[StructLayout(LayoutKind.Sequential)]public class AIKIT_CustomData
{public IntPtr Next;public byte[] KeyBytes;public IntPtr Value;public IntPtr Reserved;public int Index;public int Len;public int From;public AIKIT_CustomData(string key, IntPtr value, int from, int index = 0, int len = 0){KeyBytes = Encoding.UTF8.GetBytes(key + "\0");Value = value;From = from;Index = index;Len = len;Reserved = IntPtr.Zero;Next = IntPtr.Zero;}}
[StructLayout(LayoutKind.Sequential)]
public class AIKIT_HANDLE
{public IntPtr usrContext;public string abilityID;public IntPtr handleID;
}
[StructLayout(LayoutKind.Sequential)]
public class AIKITBuilderHandle
{public IntPtr builderInst;public BuilderType type;
}public enum BuilderType
{BUILDER_TYPE_PARAM,BUILDER_TYPE_DATA
}
public enum BuilderDataType
{DATA_TYPE_TEXT,           // 文本DATA_TYPE_AUDIO,          // 音频DATA_TYPE_IMAGE,          // 图片DATA_TYPE_VIDEO           // 视频
}
[StructLayout(LayoutKind.Sequential)]
public class AIKIT_BaseData
{public IntPtr next; // 链表指针,使用IntPtr代替void*指针public AIKIT_BaseParam desc; // 指针,使用IntPtr代替const char*public string key;public IntPtr value; // 指针,使用IntPtr代替void*public IntPtr reserved; // 预留字段,使用IntPtr代替void*public int len; // 数据长度public int type; // 变量类型public int status; // 变量类型public int from; // 变量类型
}[StructLayout(LayoutKind.Sequential)]
public class BuilderData
{public int type;    // 数据类型public string name; // 数据段名public byte[] data; // 数据段实体(当送入路径时,此处传入路径地址字符串指针即可;public int len;     // 数据段长度(当送入路径或文件句柄时,此处传0即可)public int status;  // 数据段状态,参考AIKIT_DataStatus枚举
}
// 定义委托类型
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void AIKIT_OnOutput(IntPtr handle, AIKIT_BaseDataList output);
public delegate void AIKIT_OnEventDelegate(IntPtr handle, int eventType, IntPtr eventValue);
public delegate void AIKIT_OnErrorDelegate(IntPtr handle, int err, IntPtr desc);// 定义与C++结构体对应的C#结构体// 定义与C++结构体对应的C#结构体
[StructLayout(LayoutKind.Sequential)]
public struct AIKIT_Callbacks
{public AIKIT_OnOutput outputCB;public AIKIT_OnEventDelegate eventCB;public AIKIT_OnErrorDelegate errorCB;
}[StructLayout(LayoutKind.Sequential)]
public class AIKIT_BaseDataList
{public IntPtr node;//public AIKIT_BaseData node;public int count;public int totalLen;
}
  • 条件
    • 三个库文件
    • 一个唤醒词文件

先将官方的sdk中的三个库文件导入到unity中(项目assets文件夹中创建一个bin文件夹/放在plugins中),两个dll,一个lib
然后在项目中的某个路径下添加唤醒词文件(txt)
记得唤醒词文件的路径,在代码中需要获取那个文件
然后就可以直接运行测试了
我本地跑了很多次都是成功的,有问题可以留言

待解决的问题

  • 唤醒成功后的回调对象的转换还有问题
  • 哪些部分是初始化后不需要每次都调用的要和每次都需要调用的函数进行隔离
  • 代码整理,我将所有的逻辑都放在了一个类中,很不雅观

这次的代码之旅让我对指针和c++有了深刻的理解,在这几天的不断尝试中,我几度想放弃,网上关于sdk调用的知识很少,客服也是一问三不知,答非所问。不过还是坚持了下来…我可真是太棒了。
下班了就先写到这里了,有时间再把具体的实现流程补充一下吧
不过如果完整的看一遍下来,应该没什么问题,主要就是时间花在了读官方提供的demo源码以及自己根据源码中提供的api去模拟demo中的一些功能。
大概的情况就是,官方的demo源码中使用了很多工厂模式去新建对象,而在提供的dll中是没有这方面的函数的,刚官方的文档也是一点没写怎么不用工厂模式去新建对象。因此需要自己根据其他的函数,去模拟创建出工厂模式创建对象的过程。
当然看网上也有很多关于自己打dll包,将工厂模式之类没开放出来的函数开放出来,然后去调用那个自己打的dll包的说法,不过感觉目前我的能力还没有到那个水平就没有去尝试,有时间的话看看能不能摸索一下吧~

这篇关于unity-调用讯飞星火语音唤醒-新版windowsSDK的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

人工和AI大语言模型成本对比 ai语音模型

这里既有AI,又有生活大道理,无数渺小的思考填满了一生。 上一专题搭建了一套GMM-HMM系统,来识别连续0123456789的英文语音。 但若不是仅针对数字,而是所有普通词汇,可能达到十几万个词,解码过程将非常复杂,识别结果组合太多,识别结果不会理想。因此只有声学模型是完全不够的,需要引入语言模型来约束识别结果。让“今天天气很好”的概率高于“今天天汽很好”的概率,得到声学模型概率高,又符合表达

ScrollView 非手动调用的方法

1. /**  *  非人为的时候调用这个方法  *  *  @param scrollView <#scrollView description#>  */ - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {           } 2.判断控制器的view是否加载过 [willShowVC

【Unity Shader】片段着色器(Fragment Shader)的概念及其使用方法

在Unity和图形编程中,片段着色器(Fragment Shader)是渲染管线中的一个阶段,负责计算屏幕上每个像素(片段)的颜色和特性。片段着色器通常在顶点着色器和任何几何处理之后运行,是决定最终像素颜色的关键步骤。 Fragment Shader的概念: 像素处理:片段着色器处理经过顶点着色器和几何着色器处理后,映射到屏幕空间的像素。颜色计算:它计算每个像素的颜色值,这可能包括纹理采样、光

【Unity Shader】Alpha Blend(Alpha混合)的概念及其使用示例

在Unity和图形编程中,Alpha Blend(也称为Alpha混合)是一种用于处理像素透明度的技术。它允许像素与背景像素融合,从而实现透明或半透明的效果。Alpha Blend在渲染具有透明度的物体(如窗户、玻璃、水、雾等)时非常重要。 Alpha Blend的概念: Alpha值:Alpha值是一个介于0(完全透明)和1(完全不透明)的数值,用于表示像素的透明度。混合模式:Alpha B

关于文章“python+百度语音识别+星火大模型+讯飞语音合成的语音助手”报错的修改

前言 关于我的文章:python+百度语音识别+星火大模型+讯飞语音合成的语音助手,运行不起来的问题 文章地址: https://blog.csdn.net/Phillip_xian/article/details/138195725?spm=1001.2014.3001.5501 1.报错问题 如果运行中报错,且报错位置在Xufi_Voice.py文件中的pcm_2_wav,如下图所示

Unity Meta Quest 开发:关闭 MR 应用的安全边界

社区链接: SpatialXR社区:完整课程、项目下载、项目孵化宣发、答疑、投融资、专属圈子 📕教程说明 这期教程我将介绍如何在应用中关闭 Quest 系统的安全边界。 视频讲解: https://www.bilibili.com/video/BV1Gm42157Zi 在 Unity 中导入 Meta XR SDK,进行环境配置后,打开 Assets > Plugins > An

在WinCE的C#编程中,需要静态调用C++的动态库,需要添加using System.Runtime.InteropServices

using System.Runtime.InteropServices;         [DllImport("Win32DLL.dll", EntryPoint = "WriteREG_SZToRegTCHAR")]         private static extern bool WriteREG_SZToRegTCHAR(int iFlag, string regKeyP

H5唤醒APP方法,H5唤醒不了App跳下载页

H5唤醒APP方法,H5唤醒不了App跳下载页 let ua = window.navigator.userAgent.toLowerCase();let src = {iphone: /iphone/i.test(ua),android: /android/i.test(ua),windows: /windows/i.test(ua),weixin: /micromessenger/i.te

PHP中静态(static)调用非静态方法详解

1.PHP中可以静态调用非静态方法么? 今天我被问到PHP中可不可以使用 className::methodName() 的方法来调用一个没有声明static的方法。在我的印象中,我好像是见过这种用法,但又有些不确定。大家都知道,在手册或者教程里,方法被分为静态方法 和非静态方法,通常我们静态调用的方法,肯定是静态方法。 那如果我们调用了非静态方法会怎么样呢?首先做测试。 1

如何利用AopContext.currentProxy()解决事务管理中的方法调用问题

在Spring应用开发中,使用AOP(面向切面编程)来管理事务是非常常见的做法。然而,在某些场景下,尤其是在同一个类的方法内部,一个非事务方法直接调用另一个带有事务注解的方法时,可能会遇到事务不生效的问题。本文将深入探讨这一问题的原因,并介绍如何通过AopContext.currentProxy()方法来有效解决这一问题。 问题背景 想象一下,你有一个服务类UserService,其中包含两个方法