(适用于.NET/.NET Core/.NET Framework)

【目录】
0.前言
1.第一个AOP程序
2.Aspect横切面编程
3.一个横切面程序拦截多个主程序
4.多个横切面程序拦截一个主程序
5.AOP的泛型处理(扩充)
6.AOP的异步处理(扩充)
7.优势总结
8.展望

0.前言

AOP(Aspect Oriented Programming)是“面向横切面编程”,主要是用来对程序/模块进行解耦。怎么理解??

我们可以把一般的编程理解为“纵向编程”(主程序),比如如下的一个示例代码:

        public string GetInfo(int i)
{
string s = ""; if (i == 1)
s = "A";
else if (i == 2)
s = "B";
else if (i == 3)
s = "C";
else
s = "Z"; return s;
}

试想一下,上述软件实际使用后,

  • 如果条件变量i有更多的判断值,我们是不是要在GetInfo()方法内部修改代码+重新编译?
  • 如果后续需要加个日志记录功能,我们是不是也要在GetInfo()方法内部加上日志函数+重新编译?
  • 如果...
  • 更多如果...

为了避免上述的这些麻烦并增加软件的灵活性,“横向编程”,也就是AOP被创造了出来,它就像是“横切一刀”,把相关功能塞进了主程序。

现行AOP的实现,主要是通过拦截方法(即拦截主程序),并修改其参数+返回值来完成。

网上有很多相关方案,比如:特性注释拦截、动态代码生成、派遣代理模式、等。但这些方案要么实现的很复杂、要么耦合度没完全切断、逻辑有变化时还是需要修改代码重新编译。均不够理想。

而今天要隆重登场的主角-DeveloperSharp平台中的AOP技术,则提供了一种简便、快捷、彻底解耦的AOP实现。使用它,在程序逻辑有变化时,你只需要修改配置文件就行,而不再需要对主程序进行一丁丁点的代码修改!!

1.第一个AOP程序

制作一个AOP程序需要四个步骤:

(1)制作主程序

(2)制作横切面程序

(3)制作配置文件,让横切面程序拦截主程序

(4)调用主程序

下面,我们一步一步来实现上述四个步骤。

【第一步】:制作主程序

我们在Visual Studio中新建一个名为“School.Logic”的类库工程,并在该工程中新建一个名为PersonManage的类,该类中有一个名为GetInfo1的方法,代码如下:

//从NuGet引用DeveloperSharp包
using DeveloperSharp.Structure.Model; namespace School.Logic
{
//主程序必须继承自LogicLayer类
public class PersonManage : LogicLayer
{
public string GetInfo1(string Name, int Num)
{
return $"共有{Name}{Num}人";
}
}
}

以上,编写了一个非常简单的主程序。

【第二步】:制作横切面程序

我们再在Visual Studio中新建一个名为“School.Aspect”的类库工程,并在该工程中新建一个名为Interceptor1的类,代码如下:

//从NuGet引用DeveloperSharp包
using DeveloperSharp.Structure.Model.Aspect; namespace School.Aspect
{
//横切面程序必须继承自AspectModel类
public class Interceptor1 : AspectModel
{
//PreProcess方法先于主程序执行
public override void PreProcess(object sender, AspectEventArgs e)
{
//把主程序的两个参数值改掉
e.MethodInfo.ParameterValues[0] = "老师";
e.MethodInfo.ParameterValues[1] = 20;
} //PostProcess方法后于主程序执行
public override void PostProcess(object sender, AspectEventArgs e)
{ }
}
}

以上,编写了一个横切面程序。它的主要功能是把主程序方法的两个参数值给改掉。
AspectModel基类中的PreProcess方法会在主程序方法执行之前被执行,而PostProcess方法会在主程序方法执行之后被执行。它两就是AOP横向拦截的核心要素。它两均需要被override重写覆盖掉。

【第三步】:制作配置文件,让横切面程序拦截主程序

若是在.Net Core环境下,我们创建一个名为DeveloperSharp.json的配置文件,设置让Interceptor1拦截PersonManage中的GetInfo1方法。文件内容如下:

{
"DeveloperSharp":
{
"AspectObject":
[
{
"name":"School.Aspect.Interceptor1", //横切面拦截器类
"scope":"School.Logic.PersonManage", //被拦截的主程序类
"method":"GetInfo1" //被拦截的方法
}
]
}
}

若是在.Net Framework环境下,我们创建一个名为DeveloperSharp.xml的配置文件,设置让Interceptor1拦截PersonManage中的GetInfo1方法。文件内容如下:

<?xml version="1.0" encoding="utf-8" ?>
<DeveloperSharp>
<AspectObject>
<Ao name="School.Aspect.Interceptor1" scope="School.Logic.PersonManage" method="GetInfo1"/>
</AspectObject>
</DeveloperSharp>

注意:以上配置中所有的类名,都要用完全限定名。

【第四步】:调用主程序

最后,我们再在Visual Studio中创建一个控制台工程,让它来调用主程序中的GetInfo1方法,代码如下:

        //需要引用School.Aspect、School.Logic、DeveloperSharp三项
static void Main(string[] args)
{
var pm = new School.Logic.PersonManage(); //要用这种形式调用主程序中的方法,AOP功能才会生效
var str = pm.InvokeMethod("GetInfo1", "学生", 200);
Console.WriteLine(str); Console.ReadLine();
}

附注:有人会觉得上述InvokeMethod这种调用方法不够优雅,但事实上ASP.NET Web Api也是被类似InvokeMethod这种方式包裹调用才实现了各种Filter拦截器的拦截(本质也是AOP),只不过它的这个InvokeMethod动作是在.NET自身的CLR管道运行时中进行的。而且,那些Filter拦截器还只能用于ASP.NET Web Api环境,而不能像本方案这样用于一般程序。

现在,为了让前面第三步创建的配置文件生效,我们此时还需要在此主调项目中对它进行链接:
若是在.Net Core环境下,我们只需要把DeveloperSharp.json文件放到程序执行目录中(即bin目录下与dll、exe等文件的同一目录中,放错了位置会报错)(注意:有些.Net Core版本在Visual Studio“调试”时,不会在bin目录下生成全部的dll、exe,此时需要把此配置文件放在应用程序的“根目录”下)。

若是在.Net Framework环境下,我们需要在工程配置文件App.config/Web.config中添加appSettings节点,节点内容如下:

  <appSettings>
<add key="ConfigFile" value="D:\Test\Assist\DeveloperSharp.xml" />
</appSettings>

此处需要设置为配置文件的“绝对路径”(使用“绝对路径”而不是“相对路径”,一是有利于安全性,二是有利于分布式部署)

一切准备完毕,运行,结果如下:

【控制台显示出】:共有老师20人

可见AOP已经拦截成功。

若此时,我们在配置文件DeveloperSharp.json/DeveloperSharp.xml中稍做修改,比如:把“GetInfo1”这个方法名改为“ABC”这样一个不存在的方法名,再运行,结果如下:

【控制台显示出】:共有学生200人

2.Aspect横切面编程

上面,第二步,制作的横切面程序,是通过修改主程序方法的参数值,而最终改变了主程序的返回值。

其实,我们也有办法直接修改主程序方法的返回值,比如把上面Interceptor1类的代码修改为如下:

//从NuGet引用DeveloperSharp包
using DeveloperSharp.Structure.Model.Aspect; namespace School.Aspect
{
//横切面程序必须继承自AspectModel类
public class Interceptor1 : AspectModel
{
//PreProcess方法先于主程序执行
public override void PreProcess(object sender, AspectEventArgs e)
{ } //PostProcess方法后于主程序执行
public override void PostProcess(object sender, AspectEventArgs e)
{
//把主程序的返回值改掉
e.MethodInfo.ReturnValue = $"共有校长2人";
}
}
}

运行,结果如下:

【控制台显示出】:共有校长2人

到目前为止,我们已经知道了如何通过“Aspect横切面程序”修改主程序方法的参数值、返回值。

如果我们想进一步获取主程序的“命名空间”、“类名”、“方法名”、“参数名”、“参数类型”、“返回值类型”,则可以通过如下代码获取:

e.MethodInfo.NamespaceName                       //命名空间
e.MethodInfo.ClassName //类名
e.MethodInfo.MethodName //方法名
e.MethodInfo.ParameterInfos[0].Name //参数名(第一个参数)
e.MethodInfo.ParameterInfos[0].ParameterType //参数类型(第一个参数)
e.MethodInfo.ReturnValue.GetType() //返回值类型

有时候,在某些特殊情况下,我们希望主程序方法不运行,此时则可以通过在PreProcess方法里把e.Continue设置为false来完成。

接前面的“第一个AOP程序”,比如:我们希望当人数大于10000时,主程序方法就不再运行,则可以通过把Interceptor1类的代码修改为如下样式来实现:

//从NuGet引用DeveloperSharp包
using DeveloperSharp.Structure.Model.Aspect; namespace School.Aspect
{
//横切面程序必须继承自AspectModel类
public class Interceptor1 : AspectModel
{
//PreProcess方法先于主程序执行
public override void PreProcess(object sender, AspectEventArgs e)
{
//当人数大于10000时,主程序方法就不再运行
if (Convert.ToInt32(e.MethodInfo.ParameterValues[1]) > 10000)
e.Continue = false;
} //PostProcess方法后于主程序执行
public override void PostProcess(object sender, AspectEventArgs e)
{ }
}
}

现在的这个示例是一个Aspect横切面程序拦截一个主程序。在后续将要讲解的“多个Aspect横切面程序拦截一个主程序”的情况中,只要有一个e.Continue=false被设置,主程序方法就不会运行(在此事先提点)。

3.一个横切面程序拦截多个主程序

为了演示这部分的内容,我们首先在前面“第一个AOP程序”的基础上,把主程序进行扩充。采取的动作是:

(1)在PersonManage类中增加一个GetInfo2方法

(2)再新增一个主程序类SystemManage,该类中有一个名为GetMessage1的方法。代码如下:

PersonManage类:

//从NuGet引用DeveloperSharp包
using DeveloperSharp.Structure.Model; namespace School.Logic
{
//主程序必须继承自LogicLayer类
public class PersonManage : LogicLayer
{
public string GetInfo1(string Name, int Num)
{
return $"共有{Name}{Num}人";
} public string GetInfo2(string Name, int Num)
{
return $"学校共有{Name}{Num}人";
}
}
}

SystemManage类:

//从NuGet引用DeveloperSharp包
using DeveloperSharp.Structure.Model; namespace School.Logic
{
//主程序必须继承自LogicLayer类
public class SystemManage : LogicLayer
{
public string GetMessage1(string Name1, int Num1, string Name2, int Num2)
{
return $"第一组共有{Name1}{Num1}人,第二组共有{Name2}{Num2}人";
}
}
}

如此一来,现在就有了3个主程序方法。

接下来,我们修改配置文件,让Interceptor1去拦截上述的3个主程序方法。

若是在.Net Core环境下,DeveloperSharp.json文件的内容修改为如下:

{
"DeveloperSharp":
{
"AspectObject":
[
{
"name":"School.Aspect.Interceptor1",
"scope":"School.Logic.PersonManage",
"method":"*" //星号*代表该作用域下的全部方法
},
{
"name":"School.Aspect.Interceptor1",
"scope":"School.Logic.SystemManage",
"method":"GetMessage1"
}
]
}
}

若是在.Net Framework环境下,DeveloperSharp.xml文件的内容修改为如下:

<?xml version="1.0" encoding="utf-8" ?>
<DeveloperSharp>
<AspectObject>
<Ao name="School.Aspect.Interceptor1" scope="School.Logic.PersonManage" method="*"/>
<Ao name="School.Aspect.Interceptor1" scope="School.Logic.SystemManage" method="GetMessage1"/>
</AspectObject>
</DeveloperSharp>

最后,我们把控制台启动程序修改为如下:

        //需要引用School.Aspect、School.Logic、DeveloperSharp三项
static void Main(string[] args)
{
var pm = new School.Logic.PersonManage();
var sm = new School.Logic.SystemManage(); //要用这种形式调用主程序中的方法,AOP功能才会生效
var str1 = pm.InvokeMethod("GetInfo1", "学生", 200);
var str2 = pm.InvokeMethod("GetInfo2", "学生", 200);
var str3 = sm.InvokeMethod("GetMessage1", "学生", 200, "院士", 10);
Console.WriteLine(str1);
Console.WriteLine(str2);
Console.WriteLine(str3); Console.ReadLine();
}

运行结果如下:

【控制台显示出】:

共有老师20人
学校共有老师20人
第一组共有老师20人,第二组共有院士10人

可见AOP所有拦截均已成功!

4.多个横切面程序拦截一个主程序

为了演示这部分的内容,我们还是要先回到前面的“第一个AOP程序”,在它的基础上,我们新增一个名为Interceptor2的Aspect横切面类,代码如下:

//从NuGet引用DeveloperSharp包
using DeveloperSharp.Structure.Model.Aspect; namespace School.Aspect
{
//横切面程序必须继承自AspectModel类
public class Interceptor2 : AspectModel
{
//PreProcess方法先于主程序执行
public override void PreProcess(object sender, AspectEventArgs e)
{
//把主程序的两个参数值改掉
e.MethodInfo.ParameterValues[0] = "辅导员";
e.MethodInfo.ParameterValues[1] = 40;
} //PostProcess方法后于主程序执行
public override void PostProcess(object sender, AspectEventArgs e)
{ }
}
}

如此一来,我们就有了2个Aspect横切面程序Interceptor1与Interceptor2。

接下来,我们修改配置文件,让Interceptor1、Interceptor2都去拦截主程序方法GetInfo1。

若是在.Net Core环境下,DeveloperSharp.json文件的内容修改为如下:

{
"DeveloperSharp":
{
"AspectObject":
[
{
"name":"School.Aspect.Interceptor1",
"scope":"School.Logic.PersonManage",
"method":"GetInfo1"
},
{
"name":"School.Aspect.Interceptor2",
"scope":"School.Logic.PersonManage",
"method":"GetInfo1"
}
]
}
}

若是在.Net Framework环境下,DeveloperSharp.xml文件的内容修改为如下:

<?xml version="1.0" encoding="utf-8" ?>
<DeveloperSharp>
<AspectObject>
<Ao name="School.Aspect.Interceptor1" scope="School.Logic.PersonManage" method="GetInfo1"/>
<Ao name="School.Aspect.Interceptor2" scope="School.Logic.PersonManage" method="GetInfo1"/>
</AspectObject>
</DeveloperSharp>

上述修改完毕,运行控制台主调程序,结果如下:

【控制台显示出】:共有辅导员40人

从上述运行结果,我们大致可以推断出:Interceptor1、Interceptor2这两个Aspect横切面拦截器是按配置顺序执行的。其中,Interceptor1先把GetInfo1方法的两个参数值改为了("老师",20),接着,Interceptor2又把GetInfo1方法的两个参数值改为了("辅导员",40),所以最终GetInfo1方法的参数值变为了("辅导员",40)。

5.AOP的泛型处理
如果我们的主程序是泛型方法,则需要用InvokeMethod<T>这种方式来进行调用。

比如,现有如下的主程序代码:

//从NuGet引用DeveloperSharp包
using DeveloperSharp.Structure.Model; namespace Test4Logic
{
//主程序必须继承自LogicLayer类
public class Calculate : LogicLayer
{
public int add(int i, int j)
{
return i + j;
} public int add(int i, int j, int k)
{
return i + j + k;
} public string add<T>(T i, T j, T k)
{
return "T" + i.ToString() + j.ToString() + k.ToString();
} public string add<T, V>(T i, T j, V k)
{
return "TTV" + i.ToString() + j.ToString() + k.ToString();
} public string add<T, V>(T i, V j, V k)
{
return "TVV" + i.ToString() + j.ToString() + k.ToString();
}
}
}

对应的Aspect横切面类代码如下:

//从NuGet引用DeveloperSharp包
using DeveloperSharp.Structure.Model.Aspect; namespace Test4Aspect
{
//横切面程序必须继承自AspectModel类
public class Interceptor : AspectModel
{
//PreProcess方法先于主程序执行
public override void PreProcess(object sender, AspectEventArgs e)
{
//把主程序的第一个参数值改掉
e.MethodInfo.ParameterValues[0] = 8; } //PostProcess方法后于主程序执行
public override void PostProcess(object sender, AspectEventArgs e)
{ }
}
}

对应的配置文件如下:
若是在.Net Core环境下,DeveloperSharp.json文件的内容如下:

{
"DeveloperSharp":
{
"AspectObject":
[
{
"name":"Test4Aspect.Interceptor", //横切面拦截器类
"scope":"Test4Logic.Calculate", //被拦截的主程序类
"method":"add" //被拦截的方法
}
]
}
}

若是在.Net Framework环境下,DeveloperSharp.xml文件的内容如下:

<?xml version="1.0" encoding="utf-8" ?>
<DeveloperSharp>
<AspectObject>
<Ao name="Test4Aspect.Interceptor" scope="Test4Logic.Calculate" method="add"/>
</AspectObject>
</DeveloperSharp>

控制台主调程序代码如下:

        //需要引用Test4Aspect、Test4Logic、DeveloperSharp三项
static void Main(string[] args)
{
var cal = new Test4Logic.Calculate(); //要用这种形式调用主程序中的方法,AOP功能才会生效
var r1 = cal.InvokeMethod("add", 1, 2);
var r2 = cal.InvokeMethod("add", 1, 2, 3);
var r3 = cal.InvokeMethod<int>("add", 1, 2, 3);
var r4 = cal.InvokeMethod<int, float>("add", 1, 2, (float)3);
var r5 = cal.InvokeMethod<int, float>("add", 1, (float)2, (float)3); Console.WriteLine(r1);
Console.WriteLine(r2);
Console.WriteLine(r3);
Console.WriteLine(r4);
Console.WriteLine(r5); Console.ReadLine();
}

运行上述控制台主调程序,结果如下:

【控制台显示出】:
10
13
T823
TTV823
TVV823

主程序中每个泛型方法的对应调用一目了然。

6.AOP的异步处理
如果我们的主程序是异步方法,还是使用InvokeMethod来进行调用。下面给出一个代码样式示例(代码做了简化处理):

//主程序

    //主程序必须继承自LogicLayer类
public class UserService : LogicLayer
{
public async Task<Worker> GetUser(string Id, int Age, string Name)
{
//...相关代码...
} public async Task<T> GetUser<T>(string Id, int Age, string Name) where T : User, new()
{
//...相关代码...
}
}
----------------------------------------------------------------------
//主调程序 var us = new UserService(); //要用这种形式调用主程序中的方法,AOP功能才会生效
var worker = await us.InvokeMethod("GetUser", "C007", 26, "alex");
var user = await us.InvokeMethod<Manager>("GetUser", "A002", 46, "kevin"); Console.WriteLine(worker.Name);
Console.WriteLine(user?.Name);

即然主程序可以是异步的,那Aspect横截面拦截程序能不能也是异步的了?答案是肯定的。你可以把PreProcess与PostProcess中的至少一个改为异步方法,实现单个Aspect类的同步异步混用,其代码结构与原先的同步Aspect类一致,这点连.NET/微软自身都还没有实现...
下面给出一个示例代码:

    //横切面程序必须继承自AspectModel类
public class UserInterceptor : AspectModel
{
//PreProcess方法先于主程序执行
public override void PreProcess(object sender, AspectEventArgs e)
{ } //PostProcess方法后于主程序执行
public override async void PostProcess(object sender, AspectEventArgs e)
{
await Task.Run(() =>
{
Thread.Sleep(10000);
File.AppendAllText("D:/zzz.txt", "耗时操作");
});
}
}

7.优势总结

本文所讲述的,是全网唯一实现AOP彻底解耦的技术方案。使用它,当你需要给主程序增加Aspect横切面拦截器时,无论是增加一个还是多个,都不再需要修改&重新编译主程序。这实现了不同功能构件之间的0依赖拼装/拆卸开发方式,随之而来的也会对研发团队的管理模式产生重大影响,意义深远...

8.展望

AOP对于程序代码的解耦、业务模块的拆分与拼装组合,有着巨大的作用。正确的使用AOP,甚至能对传统的软件架构设计,产生颠覆性的影响,如超级战士出场一般,让所有人刮目相看,完全耳目一新!!

为了让读者能直观感知AOP的上述神奇魅力,下面给出一个业务案例:
有一批货品要录入数据库,货品包含长、宽、高、颜色、类型等属性。现在有业务需求如下,
(1)当货品长度大于10厘米时,它在数据库中标记为A类;当货品长度大于20厘米时,标记为B类。
(2)当货品颜色无法分辨时,统一在数据库中默认标记为白色。
(3)每个货品录入数据库后,还要在另一个财务数据库中录入该货品的价格信息,同时把该货品的操作员名字记入日志文件。

这样的一个业务案例,你以前会怎么设计这个程序?今天学了AOP后你又会怎么设计程序?你会创建几个Aspect横切面了...?

 


原文首发于下方公众号,请关注!

向大佬学习,探行业内幕,享时代机遇。

C#的AOP(最经典实现)的更多相关文章

  1. 《Spring 5官方文档》 Spring AOP的经典用法

    原文链接 在本附录中,我们会讨论一些初级的Spring AOP接口,以及在Spring 1.2应用中所使用的AOP支持. 对于新的应用,我们推荐使用 Spring AOP 2.0来支持,在AOP章节有 ...

  2. AOP编程实践总结

    AOP编程实践总结 AOP概述 AOP(Aspect-Oriented Programming,面向方面编程)是OOP(Object-Oriented Programing,面向对象编程)的补充和完善 ...

  3. javascript设计模式——装饰者模式

    前面的话 在程序开发中,许多时候都并不希望某个类天生就非常庞大,一次性包含许多职责.那么可以使用装饰者模式.装饰者模式可以动态地给某个对象添加一些额外的职责,而不会影响从这个类中派生的其他对象.本文将 ...

  4. spring三大框架整合

        Spring概述 Spring介绍 Spring它是一个一站式的分层轻量级框架. Spring体系结构 1.      core container a)        beans与core ...

  5. JavaScript设计模式(装饰者模式)

    一.模拟传统面向对象语言的装饰者模式: 假设我们在编写一个飞机大战的游戏,随着经验值的增加,我们操作的飞机对象可以升级成更厉害的飞机,一开始这些飞机只能发射普通的子弹,升到第二级时可以发射导弹,升到第 ...

  6. AOP基本概念、AOP底层实现原理、AOP经典应用【事务管理、异常日志处理、方法审计】

    1 什么是AOP AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.AOP是OOP的延续,是软件 ...

  7. AOP经典2种配置演示样例

    第一种: 使用aop指定切面aspect. <bean id="LogAdvice" class="com.thinkmore.framework.monitor. ...

  8. Spring的IOC和AOP之深剖

    今天,既然讲到了Spring 的IOC和AOP,我们就必须要知道 Spring主要是两件事: 1.开发Bean:2.配置Bean.对于Spring框架来说,它要做的,就是根据配置文件来创建bean实例 ...

  9. Spring AspectJ基于注解的AOP实现

    对于AOP这种编程思想,很多框架都进行了实现.Spring就是其中之一,可以完成面向切面编程.然而,AspectJ也实现了AOP的功能,且实现方式更为简捷,使用更加方便,而且还支持注解式开发.所以,S ...

  10. Spring中Aop的扩展及剖析

    AOP简介: 面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范 ...

随机推荐

  1. 【Azure 应用服务】在创建App Service时,遇见“No available instances to satisfy this request. App Service is attempting to increase capacity.”错误

    问题描述 在创建新的App Service,遇见了资源不满足当前需求的提示.详细消息为: "Code": "Conflict","Message&qu ...

  2. 压测中TPS上不去的几种原因及分析?

    1. 服务器资源限制:服务器的硬件资源(如 CPU.内存.磁盘)可能不足以处理大量的请求.在高负载情况下,服务器可能无法及时响应所有的请求,导致 TPS 上不去.解决方法可以考虑升级硬件资源或通过负载 ...

  3. SpringCloudStream消息驱动

    1. 基本介绍 官方文档: https://spring.io/projects/spring-cloud-stream#learn 背景: 在一般的大型项目中,或者分布式微服务结构的系统里,一般都会 ...

  4. vue3 如果用ts,导出时候要用 defineComponent,这俩是配对的,为了类型的审查正确

    vue3 如果用ts,导出时候要用 defineComponent,这俩是配对的,为了类型的审查正确

  5. B站Aimls的JavaFx教程目录合集

    B站里有时候不太好去找资源,用JS爬了下,整出标题和链接,方便后续查询某个知识点的使用! JavaFX视频教程第1课,hello world JavaFX视频教程第2课,application的启动方 ...

  6. 主题 3 编辑器(Vim)

    主题 3 编辑器(Vim) 编辑器 (Vim) · the missing semester of your cs education (missing-semester-cn.github.io) ...

  7. 启动Eclipse 弹出Failed to load the JNI shared library jvm.dll解决方案

    原因:eclipse的版本与jdk版本不一致 解决方案:两者都安装64位的,或者都安装32位的,不能一个是32位一个是64位.

  8. 通过socket进行网络通信(服务端)

    声明:此文只是为自己方便理解,做了一些具象的比喻和假设,并不符合客观事实,谨慎阅读! ​ 在一台主机中, 两个进程想要通信可以通过一个管道(文件):一个从管道的一端写,一个从另一端读 , 然而管道是半 ...

  9. Java SE 22 新增特性

    Java SE 22 新增特性 作者:Grey 原文地址: 博客园:Java SE 22 新增特性 CSDN:Java SE 22 新增特性 源码 源仓库: Github:java_new_featu ...

  10. springboot 在 yaml 文件中读取 pom 文件的 properties

    如果没有其他配置,只能读取 yaml 文件所在模块下 和 父级模块的 pom 的 properties,以下是配置: <properties> <revision>1.0< ...