婶可忍叔不可忍的AutoMapper的你还在用吗?
AutoMapper是让人又爱又恨的项目
- 爱它是因为它解决了一些问题,因为很多项目都用了,下载量很多,受众很广。
- 恨它是因为它诸多反人类的设计
- 为此本人开源项目PocoEmit对标AutoMapper
1. AutoMapper反人类设计
1.1 AutoMapper注册代码
services.AddAutoMapper(cfg => cfg.CreateMap<User, UserDTO>());
User和UserDTO除了类名不一样,其他都一样,怎么看这行代码都多余。
需要转化的类型越多,多余的代码就越多。
类型转化不应该就是个静态方法吗?而且AutoMapper注册却依赖微软容器,
本人觉得AutoMapper设计的太反人类了。
1.2 PocoEmit对于大部分转化是不需要手动配置
- PocoEmit可以轻松的定义静态实例
- PocoEmit静态实例可以用来定义静态委托字段,当静态方法使用
UserDTO dto = PocoEmit.Mapper.Default.Convert<User, UserDTO>(new User());
2. AutoMapper的性能差强人意
2.1 以下是AutoMapper官网例子与PocoEmit.Mapper的对比
- Customer转化为CustomerDTO(嵌套多个子对象、数组及列表)
- Auto是执行AutoMapper的IMapper.Map方法
- Poco是执行PocoEmit.Mapper的IMapper.Convert方法
- PocoFunc是执行PocoEmit.Mapper生成的委托
| Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|
| Auto | 89.30 ns | 1.006 ns | 1.118 ns | 90.17 ns | 1.46 | 0.03 | 0.0260 | 448 B | 1.08 |
| Poco | 61.31 ns | 1.036 ns | 1.194 ns | 61.25 ns | 1.00 | 0.03 | 0.0241 | 416 B | 1.00 |
| PocoFunc | 42.56 ns | 0.066 ns | 0.073 ns | 42.56 ns | 0.69 | 0.01 | 0.0223 | 384 B | 0.92 |
- Auto耗时比Poco多50%左右
- Auto耗时是PocoFunc的两倍多
2.2 能不能用AutoMapper生成委托来提高性能呢
- 既可以说能也可以说不能
- 说能是因为AutoMapper确实提供了该功能
- 说不能是因为AutoMapper没打算给用户用
2.2.1 AutoMapper生成委托有点麻烦
var configuration = _auto.ConfigurationProvider.Internal();
var mapRequest = new MapRequest(new TypePair(typeof(Customer), typeof(CustomerDTO)));
Func<Customer, CustomerDTO, ResolutionContext, CustomerDTO> autoFunc = configuration.GetExecutionPlan<Customer, CustomerDTO>(mapRequest);
作为对比PocoEmit.Mapper就简单的多了
Func<Customer, CustomerDTO> pocoFunc = PocoEmit.Mapper.Default.GetConvertFunc<Customer, CustomerDTO>();
2.2.2 调用AutoMapper生成的委托更麻烦
- 参数ResolutionContext没有公开的构造函数,也找不到公开的实例
- 只能通过反射获得ResolutionContext的实例
var field = typeof(AutoMapper.Mapper).GetField("_defaultContext", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
ResolutionContext resolutionContext = field.GetValue(_auto) as ResolutionContext;
2.2.3 加入AutoMapper生成委托再对比一下
| Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|
| Auto | 89.30 ns | 1.006 ns | 1.118 ns | 90.17 ns | 1.46 | 0.03 | 0.0260 | 448 B | 1.08 |
| AutoFunc | 56.04 ns | 0.103 ns | 0.119 ns | 56.03 ns | 0.91 | 0.02 | 0.0260 | 448 B | 1.08 |
| Poco | 61.31 ns | 1.036 ns | 1.194 ns | 61.25 ns | 1.00 | 0.03 | 0.0241 | 416 B | 1.00 |
| PocoFunc | 42.56 ns | 0.066 ns | 0.073 ns | 42.56 ns | 0.69 | 0.01 | 0.0223 | 384 B | 0.92 |
- AutoMapper生成委托确实也快了不少
- 从百分比来看即使不生成委托,AutoMapper也慢不了多少?没有数量级的区别,能忍? --- 反问句
2.3 简单类型转化对比
- User转UserDTO,只有两个简单属性
| Method | Mean | Error | StdDev | Ratio | Gen0 | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|
| Auto | 35.436 ns | 0.0455 ns | 0.0505 ns | 1.57 | 0.0019 | 32 B | 0.50 |
| AutoFunc | 4.159 ns | 0.0847 ns | 0.0906 ns | 0.18 | 0.0019 | 32 B | 0.50 |
| Poco | 22.607 ns | 0.1754 ns | 0.1801 ns | 1.00 | 0.0037 | 64 B | 1.00 |
| PocoFunc | 3.818 ns | 0.0176 ns | 0.0180 ns | 0.17 | 0.0019 | 32 B | 0.50 |
- Auto耗时是AutoFunc差不多十倍,差出一个数量级了(回答了前面的反问)
- AutoFunc耗时比PocoFunc稍多,这说明AutoMapper复杂类型转化性能非常不好,简单类型转化可能还能凑合
- 关键是性能好生成的委托AutoMapper不给用啊,“婶可忍叔不可忍”啊!
3. AutoMapper生成的代码能通过代码审核吗?
3.1 还是AutoMapper官网那个例子生成以下代码
T __f<T>(System.Func<T> f) => f();
CustomerDTO _autoMap(Customer source, CustomerDTO destination, ResolutionContext context)
{
return (source == null) ?
(destination == null) ? (CustomerDTO)null : destination :
__f(() => {
CustomerDTO typeMapDestination = null;
typeMapDestination = destination ?? new CustomerDTO();
try
{
typeMapDestination.Id = source.Id;
}
catch (Exception ex)
{
throw TypeMapPlanBuilder.MemberMappingError(
ex,
default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);
}
try
{
typeMapDestination.Name = source.Name;
}
catch (Exception ex)
{
throw TypeMapPlanBuilder.MemberMappingError(
ex,
default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);
}
try
{
Address resolvedValue = null;
Address mappedValue = null;
resolvedValue = source.Address;
mappedValue = (resolvedValue == null) ? (Address)null :
((Func<Address, Address, ResolutionContext, Address>)((
Address source_1,
Address destination_1,
ResolutionContext context) => //Address
(source_1 == null) ?
(destination_1 == null) ? (Address)null : destination_1 :
__f(() => {
Address typeMapDestination_1 = null;
typeMapDestination_1 = destination_1 ?? new Address();
try
{
typeMapDestination_1.Id = source_1.Id;
}
catch (Exception ex)
{
throw TypeMapPlanBuilder.MemberMappingError(
ex,
default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);
}
try
{
typeMapDestination_1.Street = source_1.Street;
}
catch (Exception ex)
{
throw TypeMapPlanBuilder.MemberMappingError(
ex,
default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);
}
try
{
typeMapDestination_1.City = source_1.City;
}
catch (Exception ex)
{
throw TypeMapPlanBuilder.MemberMappingError(
ex,
default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);
}
try
{
typeMapDestination_1.Country = source_1.Country;
}
catch (Exception ex)
{
throw TypeMapPlanBuilder.MemberMappingError(
ex,
default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);
}
return typeMapDestination_1;
})))
.Invoke(
resolvedValue,
(destination == null) ? (Address)null :
typeMapDestination.Address,
context);
typeMapDestination.Address = mappedValue;
}
catch (Exception ex)
{
throw TypeMapPlanBuilder.MemberMappingError(
ex,
default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);
}
try
{
Address resolvedValue_1 = null;
AddressDTO mappedValue_1 = null;
resolvedValue_1 = source.HomeAddress;
mappedValue_1 = (resolvedValue_1 == null) ? (AddressDTO)null :
((Func<Address, AddressDTO, ResolutionContext, AddressDTO>)((
Address source_2,
AddressDTO destination_2,
ResolutionContext context) => //AddressDTO
(source_2 == null) ?
(destination_2 == null) ? (AddressDTO)null : destination_2 :
__f(() => {
AddressDTO typeMapDestination_2 = null;
typeMapDestination_2 = destination_2 ?? new AddressDTO();
try
{
typeMapDestination_2.Id = source_2.Id;
}
catch (Exception ex)
{
throw TypeMapPlanBuilder.MemberMappingError(
ex,
default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);
}
try
{
typeMapDestination_2.City = source_2.City;
}
catch (Exception ex)
{
throw TypeMapPlanBuilder.MemberMappingError(
ex,
default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);
}
try
{
typeMapDestination_2.Country = source_2.Country;
}
catch (Exception ex)
{
throw TypeMapPlanBuilder.MemberMappingError(
ex,
default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);
}
return typeMapDestination_2;
})))
.Invoke(
resolvedValue_1,
(destination == null) ? (AddressDTO)null :
typeMapDestination.HomeAddress,
context);
typeMapDestination.HomeAddress = mappedValue_1;
}
catch (Exception ex)
{
throw TypeMapPlanBuilder.MemberMappingError(
ex,
default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);
}
try
{
Address[] resolvedValue_2 = null;
AddressDTO[] mappedValue_2 = null;
resolvedValue_2 = source.Addresses;
mappedValue_2 = (resolvedValue_2 == null) ?
Array.Empty<AddressDTO>() :
__f(() => {
AddressDTO[] destinationArray = null;
int destinationArrayIndex = default;
destinationArray = new AddressDTO[resolvedValue_2.Length];
destinationArrayIndex = default(int);
int sourceArrayIndex = default;
Address sourceItem = null;
sourceArrayIndex = default(int);
while (true)
{
if ((sourceArrayIndex < resolvedValue_2.Length))
{
sourceItem = resolvedValue_2[sourceArrayIndex];
destinationArray[destinationArrayIndex++] = ((Func<Address, AddressDTO, ResolutionContext, AddressDTO>)((
Address source_2,
AddressDTO destination_2,
ResolutionContext context) => //AddressDTO
(source_2 == null) ?
(destination_2 == null) ? (AddressDTO)null : destination_2 :
__f(() => {
AddressDTO typeMapDestination_2 = null;
typeMapDestination_2 = destination_2 ?? new AddressDTO();
try
{
typeMapDestination_2.Id = source_2.Id;
}
catch (Exception ex)
{
throw TypeMapPlanBuilder.MemberMappingError(
ex,
default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);
}
try
{
typeMapDestination_2.City = source_2.City;
}
catch (Exception ex)
{
throw TypeMapPlanBuilder.MemberMappingError(
ex,
default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);
}
try
{
typeMapDestination_2.Country = source_2.Country;
}
catch (Exception ex)
{
throw TypeMapPlanBuilder.MemberMappingError(
ex,
default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);
}
return typeMapDestination_2;
})))
.Invoke(
sourceItem,
(AddressDTO)null,
context);
sourceArrayIndex++;
}
else
{
goto LoopBreak;
}
}
LoopBreak:;
return destinationArray;
});
typeMapDestination.Addresses = mappedValue_2;
}
catch (Exception ex)
{
throw TypeMapPlanBuilder.MemberMappingError(
ex,
default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);
}
try
{
List<Address> resolvedValue_3 = null;
List<AddressDTO> mappedValue_3 = null;
resolvedValue_3 = source.WorkAddresses;
mappedValue_3 = (resolvedValue_3 == null) ?
new List<AddressDTO>() :
__f(() => {
List<AddressDTO> collectionDestination = null;
List<AddressDTO> passedDestination = null;
passedDestination = (destination == null) ? (List<AddressDTO>)null :
typeMapDestination.WorkAddresses;
collectionDestination = passedDestination ?? new List<AddressDTO>();
collectionDestination.Clear();
List<Address>.Enumerator enumerator = default;
Address item = null;
enumerator = resolvedValue_3.GetEnumerator();
try
{
while (true)
{
if (enumerator.MoveNext())
{
item = enumerator.Current;
collectionDestination.Add(((Func<Address, AddressDTO, ResolutionContext, AddressDTO>)((
Address source_2,
AddressDTO destination_2,
ResolutionContext context) => //AddressDTO
(source_2 == null) ?
(destination_2 == null) ? (AddressDTO)null : destination_2 :
__f(() => {
AddressDTO typeMapDestination_2 = null;
typeMapDestination_2 = destination_2 ?? new AddressDTO();
try
{
typeMapDestination_2.Id = source_2.Id;
}
catch (Exception ex)
{
throw TypeMapPlanBuilder.MemberMappingError(
ex,
default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);
}
try
{
typeMapDestination_2.City = source_2.City;
}
catch (Exception ex)
{
throw TypeMapPlanBuilder.MemberMappingError(
ex,
default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);
}
try
{
typeMapDestination_2.Country = source_2.Country;
}
catch (Exception ex)
{
throw TypeMapPlanBuilder.MemberMappingError(
ex,
default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);
}
return typeMapDestination_2;
})))
.Invoke(
item,
(AddressDTO)null,
context));
}
else
{
goto LoopBreak_1;
}
}
LoopBreak_1:;
}
finally
{
enumerator.Dispose();
}
return collectionDestination;
});
typeMapDestination.WorkAddresses = mappedValue_3;
}
catch (Exception ex)
{
throw TypeMapPlanBuilder.MemberMappingError(
ex,
default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);
}
return typeMapDestination;
});
}
3.2 以下是PocoEmit.Mapper生成的代码
T __f<T>(System.Func<T> f) => f();
CustomerDTO _pocoConvert(Customer source)
{
CustomerDTO dest = null;
return (source == (Customer)null) ? (CustomerDTO)null :
__f(() => {
dest = new CustomerDTO();
int member0 = default;
string member1 = null;
Address member2 = null;
Address member3 = null;
Address[] member4 = null;
List<Address> member5 = null;
member0 = source.Id;
dest.Id = member0;
member1 = source.Name;
dest.Name = member1;
member2 = source.Address;
if ((member2 != null))
{
dest.Address = member2;
}
member3 = source.HomeAddress;
if ((member3 != null))
{
// { The block result will be assigned to `dest.HomeAddress`
AddressDTO dest_1 = null;
dest.HomeAddress = (member3 == (Address)null) ? (AddressDTO)null :
__f(() => {
dest_1 = new AddressDTO();
int member0_1 = default;
string member1_1 = null;
string member2_1 = null;
member0_1 = member3.Id;
dest_1.Id = member0_1;
member1_1 = member3.City;
dest_1.City = member1_1;
member2_1 = member3.Country;
dest_1.Country = member2_1;
return dest_1;
});
// } end of block assignment;
}
member4 = source.Addresses;
if ((member4 != null))
{
// { The block result will be assigned to `dest.Addresses`
int count = default;
AddressDTO[] dest_2 = null;
int index = default;
Address sourceItem = null;
count = member4.Length;
dest_2 = new AddressDTO[count];
index = 0;
while (true)
{
if ((index < count))
{
sourceItem = member4[index];
// { The block result will be assigned to `dest_2[index]`
AddressDTO dest_3 = null;
dest_2[index] = (sourceItem == (Address)null) ? (AddressDTO)null :
__f(() => {
dest_3 = new AddressDTO();
int member0_2 = default;
string member1_2 = null;
string member2_2 = null;
member0_2 = sourceItem.Id;
dest_3.Id = member0_2;
member1_2 = sourceItem.City;
dest_3.City = member1_2;
member2_2 = sourceItem.Country;
dest_3.Country = member2_2;
return dest_3;
});
// } end of block assignment
index++;
}
else
{
goto forLabel;
}
}
forLabel:;
dest.Addresses = dest_2;
// } end of block assignment;
}
member5 = source.WorkAddresses;
if ((member5 != null))
{
// { The block result will be assigned to `dest.WorkAddresses`
List<AddressDTO> dest_4 = null;
dest_4 = new List<AddressDTO>(member5.Count);
int index_1 = default;
int len = default;
index_1 = 0;
len = member5.Count;
while (true)
{
if ((index_1 < len))
{
Address sourceItem_1 = null;
AddressDTO destItem = null;
sourceItem_1 = member5[index_1];
// { The block result will be assigned to `destItem`
AddressDTO dest_5 = null;
destItem = (sourceItem_1 == (Address)null) ? (AddressDTO)null :
__f(() => {
dest_5 = new AddressDTO();
int member0_3 = default;
string member1_3 = null;
string member2_3 = null;
member0_3 = sourceItem_1.Id;
dest_5.Id = member0_3;
member1_3 = sourceItem_1.City;
dest_5.City = member1_3;
member2_3 = sourceItem_1.Country;
dest_5.Country = member2_3;
return dest_5;
});
// } end of block assignment;
dest_4.Add(destItem);
index_1++;
}
else
{
goto forLabel_1;
}
}
forLabel_1:;
dest.WorkAddresses = dest_4;
// } end of block assignment;
}
CustomerConvertBench.ConvertAddressCity(
source,
dest);
return dest;
});
}
3.3 简单对比如下
- AutoMapper生成代码三百多行,PocoEmit.Mapper一百多行,AutoMapper代码量是两倍以上
- AutoMapper生成大量try catch,哪怕是int对int赋值也要try
- AutoMapper用迭代器Enumerator访问列表,PocoEmit.Mapper用索引器
- AutoMapper这些区别应该是导致性能差的部分原因
3.4 如何获取AutoMapper生成的代码
LambdaExpression expression = _auto.ConfigurationProvider.BuildExecutionPlan(typeof(Customer), typeof(CustomerDTO));
3.4.1 如果要查看更可读的代码推荐使用FastExpressionCompiler
- 可以使用nuget安装
- 前面的例子就是使用FastExpressionCompiler再手动整理了一下
string code = FastExpressionCompiler.ToCSharpPrinter.ToCSharpString(expression);
3.4.2 PocoEmit获取生成代码更简单
Expression<Func<Customer, CustomerDTO>> expression = _poco.BuildConverter<Customer, CustomerDTO>();
string code = FastExpressionCompiler.ToCSharpPrinter.ToCSharpString(expression);
3.4.3 PocoEmit生成代码扩展性
- PocoEmit可以获取委托表达式自己来编译委托
- PocoEmit通过PocoEmit.Builders.Compiler.Instance来编译,可以对Instance进行覆盖来扩展
- 通过实现Compiler类,只需要重写CompileFunc和CompileAction两个方法
- 可以使用FastExpressionCompiler来实现Compiler类
4. AutoMapper枚举逻辑问题
public enum MyColor
{
None = 0,
Red = 1,
Green = 2,
Blue = 3,
}
ConsoleColor color = ConsoleColor.DarkBlue;
// Red
MyColor autoColor = _auto.Map<ConsoleColor, MyColor>(color);
// None
MyColor pocoColor = PocoEmit.Mapper.Default.Convert<ConsoleColor, MyColor>(color);
- AutoMapper先按枚举名转化,失败再按值转化,不支持的DarkBlue被AutoMapper转化为Red
- 不同类型的枚举值转化没有意义,定义枚举可以不指定值
- AutoMapper这完全是犯了画蛇添足的错误
- AutoMapper还有哪些槽点欢迎大家在评论区指出
5. PocoEmit可扩展架构
5.1 nuget安装PocoEmit可获得基础功能
- 通过PocoEmit可以读写实体的属性
- PocoEmit可以通过PocoEmit.Poco转化基础类型和枚举
- PocoEmit.Poco支持注册转化表达式
5.2 nuget安装PocoEmit.Mapper获得更多功能
- PocoEmit.Mapper可以支持PocoEmit.Poco的所有功能
- PocoEmit.Mapper可以支持自定义实体类型(不支持集合(含数组、列表及字典)成员)的转化和复制
5.3 nuget安装PocoEmit.Collections扩展集合功能
- 通过UseCollection扩展方法给PocoEmit.Mapper增加集合功能
- 扩展后PocoEmit.Mapper支持集合(含数组、列表及字典)的转化和复制
- 支持实体类型包含集合成员的转化和复制
- 嫌麻烦的同学可以直接安装PocoEmit.Collections并配置UseCollection
源码托管地址: https://github.com/donetsoftwork/MyEmit ,也欢迎大家直接查看源码。
gitee同步更新:https://gitee.com/donetsoftwork/MyEmit
如果大家喜欢请动动您发财的小手手帮忙点一下Star。
婶可忍叔不可忍的AutoMapper的你还在用吗?的更多相关文章
- 慢到不能忍?别忍了,Ubuntu 21.10 APT 源修改为华为云镜像源
更新记录 2022年4月15日:本文迁移自Panda666原博客,原发布时间:2021年3月29日. 2022年4月15日:将源改为华为云,华为云更方便.Ubuntu从20.04更新到21.10. 切 ...
- IOS 逆向工程之砸壳
在<iOS应用逆向工程>4.6.2节中,我们曾推荐使用iPhoneCake源的AppCrackr 1.7版给App砸壳.这种方式简单粗暴,省时省力,但正是因为它过于方便有木有,导致几乎所有 ...
- 使用并发 ssh 连接来提升捞日志脚本执行效率
问题背景 公司有个简单粗暴的日志服务,它部署在多台机器实例上,收集的日志记录在每台机器本地硬盘,写一个小时自动切换日志文件,硬盘空间写满了自动回卷,大约可以保存两三天的历史数据.为什么说它粗暴呢?原来 ...
- HRBUST1311 火影忍者之~忍者村 2017-03-06 16:06 106人阅读 评论(0) 收藏
火影忍者之-忍者村 忍者村是忍者聚居的村子,相等于国家的军事力量.绝大部分村民都是忍者,有一些忍者会在村内开设书店.餐厅等,不过大部分忍者都是为村子执行任务的忍者,以赚取酬劳,并于战时为国家出战. ...
- .NET Core中使用AutoMapper
何为AutoMapper AutoMapper是对象到对象的映射工具.在完成映射规则之后,AutoMapper可以将源对象转换为目标对象. 安装AutoMapper 这里我们在NuGet中下载安装Au ...
- 经典面试题之——如何自由转换两个没有继承关系的字段及类型相同的实体模型,AutoMapper?
相信很多童鞋们都被问到过这个问题,不管是在面试的时候被问过,还是笔试题里考过,甚至有些童鞋们找我要学习资料的时候我也考过这个问题,包括博主我自己,也曾被问过,而且博主现在有时作为公司的面试官,也喜欢问 ...
- 打造适合你的ABP(1)---- 完善日志系统
最近使用Abp开发了一个项目,对abp有一个大概的了解,第一个小项目接近尾声,新的项目马上开始,针对开发第一个项目中发现的问题及不方便的地方,本人做一些修改,特此记录,请大家多多指正! 本人的开发环境 ...
- 【玩转SpringBoot】翻身做主人,一统web服务器
寄人篱下的日子 一直以来受传统影响,我们的web工程总是打成war包,然后放入tomcat的webapps目录下面. 如下图01: 当tomcat启动时,会去解压war包,然后运行web工程.这大家都 ...
- CSS中@support的用法 及其calc、media用法
背景: 一次偶然的机会遇到一个朋友在刷css的库其中有这样一道题(css变量如何定义,calc, support, media),我看一眼熟悉而陌生,知其一而不知其二,叔可忍婶不可忍,马上就度娘起来, ...
- PHP webservice的使用
提到php的webservice.之前还是比较陌生的,因为接触的少呀,几乎在所有的公司中没用过,仅仅用过的一次好像是接入一个第三方的短信通道,用的是SOAP|WSDL. 一个很极端的话“webserv ...
随机推荐
- Keil watch中数据不更新解决办法
watch数据不更新解决办法 在使用keil的watch窗口观察寄存器里面的值进行调试时,有时候里面的值就是不会更新,经测试打开View中最下面的那个按键即可
- 手把手教你实现PyTorch版ViT:图像分类任务中的Transformer实战
作者:SkyXZ CSDN:SkyXZ--CSDN博客 博客园:SkyXZ - 博客园 ViT论文Arxiv地址:An Image is Worth 16x16 Words: Transformers ...
- HarmonyOS NEXT仓颉开发语言实战案例:外卖App
各位周末好,今天为大家来仓颉语言外卖App的实战分享. 我们可以先分析一下页面的布局结构,它是由导航栏和List容器组成的.幽蓝君目前依然没有找到仓颉语言导航栏的系统组件,还是要自定义,这个导航栏有三 ...
- Java集合--HashMap底层原理可视化,秒懂扩容、链化、树化
文章内容较长,带着疑问慢慢读. 文章对应的视频连接:https://www.bilibili.com/video/BV1wM3KzaE3d/ 哈希冲突问题如何高效解决? 1. 什么是冲突? 准确的说是 ...
- 翻译 | 阿里巴巴的Dapr实践与探索
本文原文来自Dapr Blog,作者阿里云高级技术专家敖小剑.本文是我根据自己的理解翻译了其中的一部分并加了一些参考文献中的内容,所以并非完整翻译,点击此处即可阅读原版英文全文. 1 关于Dapr D ...
- Xamarin.Android -- EditText输入无法实时显示问题
参考文章:EditText输入内容不显示_edittext输入没有显示-CSDN博客 https://blog.csdn.net/guodashen007/article/details/108768 ...
- nestjs微信小程序登录授权
前言 nestjs官方文档是英文,太难搞了,摸索了两天 ,把经验记下来. 以后备用 目录结构 |--src //项目根目录 |--modules // 模块 比如用户模块,商品模块 |--app // ...
- 前端开发系列032-基础篇之DOM
本文将详细介绍DOM相关的知识点,包括但不限于Document文档结构.Node节点.Node节点的类型.Node节点的关系以及DOM的基本操作( 节点的获取.节点的创建.节点的插入.节点的克隆和删除 ...
- from gi.repository import Gtk, GObject
Traceback (most recent call last): File "/usr/bin/software-center", line 25, in from gi.re ...
- 通过Web ETL统一调度和管理DataX任务
DataX是一款功能强大的数据集成平台,但是其无WEB管理界面使得DataX任务在管理与调度方面存在不少问题,任务多了后很难管理和维护,同时使用和学习成本也比较高.今天就介绍下用RestCloud E ...