我居然手写了Spring框架

手写完了

刚参加工作那会接触java还是用的struct的时代,后面在SSH火爆时代的时候我转战.net,多年之后公司转java技术栈已经是Spring的天下,源码嚼了很多遍于是很想尝试把这套东西用在.net平台上。社区有个Spring.net项目已经多年不维护了,而且还是xml配置模式非基于注解的,无法与现有的SpringBoot项目同日而语。在SpringBoot项目中的常用的注解和扩展机制我都在这个项目中实现了,可以看下面介绍的已实现的功能一览!

  • Annotation是注解的意思,在java项目里面 注解的概念和 csharp里面的 Attribute 的概念是一样的。

  • 本项目是基于Autofac(巨人的肩膀)的基础之上构建,选择用Autofac是它扩展性非常好,在实现Spring的细节上提供了便捷

  • 本项目的所有实现都参考Spring的设计思想,但是并不是纯粹的把java的代码换成csharp,功能上效果是和Spring看齐的,但代码实现上是自己实现的

本项目的目的

基于参考 Java的 Spring注解方式开发思想,

所有容器的注册 和 装配 都是依赖标签来完成。

这样一来 一方面很容易分清楚 哪些是DI 哪些非DI, 哪些是拦截器,哪些需要拦截器,轻松实现切面编程,
代码也好看,吸收java的spring框架的优越的地方,配合.net语法的优越性,编程效率能够大大提升。

本篇文章主要介绍高阶玩法,基础玩法可以看项目wiki

  • 开源地址:https://github.com/yuzd/Autofac.Annotation

支持的标签一览

标签名称使用位置使用说明
AutoConfiguration打在class上面自动装配class里面带有Bean标签的方法
Bean打在方法上面配合AutoConfiguration标签使用
Component打在class上面自动注册
Autowired打在构造方法的Parameter,类的Property,类的Field自动装配
PropertySource打在class上面配合Value标签使用,设置Value的数据源,支持json,xml,支持资源内嵌
Value打在构造方法的Parameter,类的Property,类的Field静态/动态(例如nacos)数据装配,支持强大的EL表达式
Pointcut打在class上面切面配置,一个切面拦截N多个对象,配合Before After AfterReturn AfterThrows Around 实现拦截器链
Import打在继承了ImportSelector的class上面扩展注册Component
Order打在了class上面,和Compoment一起使用值越小的越先加载
Conditional打在class或者方法上面条件加载,自定义实现的
ConditionOnBean打在标有Bean注解的方法上面条件加载
ConditionOnMissingBean打在标有Bean注解的方法上面条件加载
ConditionOnClass打在class或者方法上面条件加载
ConditionOnMissingClass打在class或者方法上面条件加载
ConditionOnProperty打在class或者方法上面条件加载
ConditionOnProperties打在class或者方法上面条件加载
PostConstruct打在方法上面当类初始化完成后调用
PreDestory打在方法上面当容器Dispose前调用

基本使用略过

基本使用可以参考详细的wiki文档:
Wiki

下面讲讲高阶玩法

1. 拦截器原理简单介绍

用了Castle.Core组件 把你想要实现拦截器的目标类生成一个代理类。
然后织入拦截器,有2种方式

  1. 类拦截器:class + 方法为virtual的方式

  • 这种方式需要 从容器中是根据一个classType来获取到目标实例

  • 接口型拦截器:interface + 方法重写的方式

    • 这种方式需要 从容器中是根据一个interfaceType来获取到目标实例

    拦截器开关

    在你想要实现拦截的目标类上打开开关 【[Component(EnableAspect = true)]】,如上面的解释,打开Aspect开关标识这个class你想要走代理包装,还可以根据InterceptorType属性值设定你是哪种方式的拦截器

    InterceptorType属性解释
    Class使用class的虚方法模式 【默认方式】
    Interface使用接口模式

    目的是打个标签就能够拦截目标方法

    使得我们自定义的方法能够

    • 在指定的目标方法执行之前先执行(比如参数校验)

    • 或者在指定的目标方法执行之后执行(比如说检验返回值,或其他收尾工作)

    • 或者环绕目标的方法,比如日志or事务:TransactionScope或者记录方法执行的时间或者日志

    拦截器标签拦截器类型使用说明
    AspectArround(抽象标签类)环绕拦截重写OnInvocation方法
    AspectBefore(抽象标签类)前置拦截器重写Before方法
    AspectAfter(抽象标签类)后置拦截器(不管目标方法成功失败都会执行)重写After方法
    AspectAfterReturn(抽象标签类)后置拦截器(只有目标方法成功才会执行)重写AfterReturn方法
    AspectAfterThrows(抽象标签类)错误拦截器(只有目标方法失败才会执行)重写AfterThrows方法

    每个拦截器方法都有一个

    拦截器的方法参数 AspectContext 属性说明

    名称说明
    ComponentContextDI容器,可以从中取得你已注册的实例
    Arguments目标方法的参数
    TargetMethod目标方法的MethodInfo
    ReturnValue目标方法的返回
    Method目标方法的代理方法MethodInfo

    前置拦截器 (Before)

    1. 首先要自己写一个类继承 前置拦截器AspectBefore(抽象标签类)

    2. 实现该抽象类的Before方法

    public class TestHelloBefore:AspectBefore{public override Task Before(AspectContext aspectContext){Console.WriteLine("TestHelloBefore");return Task.CompletedTask;}}[Component(EnableAspect = true)]//注意这里需要打开开关 否则无效public class TestHello{[TestHelloBefore]public virtual void Say(){Console.WriteLine("Say");}}
    

    前置拦截器方法的执行顺序为:先执行 TestHelloBefor的Before方法再执行你的Say方法

    后置拦截器 (After) 不管目标方法成功还是抛异常都会执行

    1. 首先要自己写一个类继承后置拦截器AspectAfter(抽象标签类)

    2. 实现该抽象类的After方法

        public class TestHelloAfter:AspectAfter{//这个 returnValue 如果目标方法正常返回的话 那就是目标方法的返回值// 如果目标方法抛异常的话 那就是异常本身public override Task After(AspectContext aspectContext,object returnValue){Console.WriteLine("TestHelloAfter");return Task.CompletedTask;}}[Component(EnableAspect = true)]public class TestHello{[TestHelloAfter]public virtual void Say(){Console.WriteLine("Say");}}
    

    执行顺序为:先执行你的SayAfter方法再执行 TestHelloAfter的After方法

    这里要特别注意的是 After 拦截器 是不管你的目标方法(SayAfter是成功还是抛异常)
    都被会执行到的

    成功返回拦截器 (AfterReturn)只有目标方法成功的时候才会执行

    1. 首先要自己写一个类继承拦截器AspectReturn(抽象标签类)

    2. 实现该抽象类的After方法

    public class TestHelloAfterReturn:AspectAfterReturn{//result 是目标方法的返回 (如果目标方法是void 则为null)public override Task AfterReturn(AspectContext aspectContext, object result){Console.WriteLine("TestHelloAfterReturn");return Task.CompletedTask;}}[Component(EnableAspect = true)]public class TestHello{[TestHelloAfterReturn]public virtual void Say(){Console.WriteLine("Say");}}
    

    执行顺序为:先执行你的Say方法再执行 TestHelloAfterReturn的AfterReturn方法

    如果你的Say方法抛出异常那么就不会执行TestHelloAfterReturn的AfterReturn方法

    异常拦截器 (AfterThrows)

    1. 首先要自己写一个类继承拦截器AspectReturn(抽象标签类)

    2. 实现该抽象类的After方法

    public class TestHelloAfterThrows:AspectAfterThrows{public override Task AfterThrows(AspectContext aspectContext, Exception exception){Console.WriteLine(exception.Message);return Task.CompletedTask;}}[Component(EnableAspect = true)]public class TestHello{[TestHelloAfterThrows]public virtual void Say(){Console.WriteLine("Say");throw new ArgumentException("exception");}}
    

    执行顺序为:先执行你的Say方法再执行 TestHelloAfterThrows的AfterThrows方法

    如果你的Say方法不抛出异常那么就不会执行 TestHelloAfterThrows的AfterThrows方法

    环绕拦截器(Around)

    注意:OnInvocation方法除了AspectContext参数以外 还有一个 AspectDelegate _next 参数,
    需要在你的Around拦截器方法显示调用 _next(aspectContext) 方法,否则目标方法不会被调用

    1. 首先要自己写一个类继承拦截器AspectArround(抽象标签类)

    2. 实现该抽象类的OnInvocation方法

    public class TestHelloAround:AspectArround{public override async Task OnInvocation(AspectContext aspectContext, AspectDelegate _next){Console.WriteLine("around start");await _next(aspectContext);Console.WriteLine("around end");}}[Component(EnableAspect = true)]public class TestHello{[TestHelloAround]public virtual void Say(){Console.WriteLine("Say");}}
    

    方法的执行顺序为:

    1. 先执行TestHelloAround的OnInvocation方法

    2. 然后TestHelloAround的OnInvocation方法里面执行的 await _next(aspectContext); 就会执行被拦截方法TestHello的Say方法;

    如果Around Befor After AfterReturn AfterThrows 一起用

    正常case

        [Component(EnableAspect = true)]public class TestHello{[TestHelloAround,TestHelloBefore,TestHelloAfter,TestHelloAfterReturn,TestHelloAfterThrows]public virtual void Say(){Console.WriteLine("Say");}}
    

    代码的执行顺序为:

    1. 先执行TestHelloAround,打印 “around start” 然后执行到里面的_next(aspectContext)会触发下面

    2. 执行TestHelloBefore 打印 “TestHelloBefore”

    3. 执行目标方法 打印 “Say”

    4. 打印 “around end” TestHelloAround运行结束

    5. 执行TestHelloAfter 打印 “TestHelloAfter”

    6. 因为是目标方法成功执行 TestHelloAfterReturn 打印 “TestHelloAfterReturn”

    由于是目标方法成功返回 没有异常,所以不会走进TestHelloAfterThrows

    异常case

        [Component(EnableAspect = true)]public class TestHello{[TestHelloAround,TestHelloBefore,TestHelloAfter,TestHelloAfterReturn,TestHelloAfterThrows]public virtual void Say(){Console.WriteLine("Say");throw new ArgumentException("exception");}}
    

    代码的执行顺序为:

    1. 先执行TestHelloAround,打印 “around start” 然后执行到里面的_next(aspectContext)会触发下面

    2. 执行TestHelloBefore 打印 “TestHelloBefore”

    3. 执行目标方法 打印 “Say”

    4. 打印 “around end” TestHelloAround运行结束

    5. 执行TestHelloAfter 打印 “TestHelloAfter”

    6. 因为是目标方法异常 执行 TestHelloAfterThrows 打印异常信息

    如上述执行顺序和spring是一致的

    多组的情况

    public class TestHelloBefore1:AspectBefore{public override Task Before(AspectContext aspectContext){Console.WriteLine("TestHelloBefore1");return Task.CompletedTask;}}public class TestHelloAfter1:AspectAfter{//这个 returnValue 如果目标方法正常返回的话 那就是目标方法的返回值// 如果目标方法抛异常的话 那就是异常本身public override Task After(AspectContext aspectContext,object returnValue){Console.WriteLine("TestHelloAfter1");return Task.CompletedTask;}}public class TestHelloAfterReturn1:AspectAfterReturn{//result 是目标方法的返回 (如果目标方法是void 则为null)public override Task AfterReturn(AspectContext aspectContext, object result){Console.WriteLine("TestHelloAfterReturn1");return Task.CompletedTask;}}public class TestHelloAround1:AspectArround{public override async Task OnInvocation(AspectContext aspectContext, AspectDelegate _next){Console.WriteLine("TestHelloAround1 start");await _next(aspectContext);Console.WriteLine("TestHelloAround1 end");}}public class TestHelloAfterThrows1:AspectAfterThrows{public override Task AfterThrows(AspectContext aspectContext, Exception exception){Console.WriteLine("TestHelloAfterThrows1");return Task.CompletedTask;}}//public class TestHelloBefore2:AspectBefore{public override Task Before(AspectContext aspectContext){Console.WriteLine("TestHelloBefore2");return Task.CompletedTask;}}public class TestHelloAfter2:AspectAfter{//这个 returnValue 如果目标方法正常返回的话 那就是目标方法的返回值// 如果目标方法抛异常的话 那就是异常本身public override Task After(AspectContext aspectContext,object returnValue){Console.WriteLine("TestHelloAfter2");return Task.CompletedTask;}}public class TestHelloAfterReturn2:AspectAfterReturn{//result 是目标方法的返回 (如果目标方法是void 则为null)public override Task AfterReturn(AspectContext aspectContext, object result){Console.WriteLine("TestHelloAfterReturn2");return Task.CompletedTask;}}public class TestHelloAround2:AspectArround{public override async Task OnInvocation(AspectContext aspectContext, AspectDelegate _next){Console.WriteLine("TestHelloAround2 start");await _next(aspectContext);Console.WriteLine("TestHelloAround2 end");}}public class TestHelloAfterThrows2:AspectAfterThrows{public override Task AfterThrows(AspectContext aspectContext, Exception exception){Console.WriteLine("TestHelloAfterThrows2");return Task.CompletedTask;}}[Component(EnableAspect = true)]public class TestHello{[TestHelloAround1(GroupName = "Aspect1",OrderIndex = 10),TestHelloBefore1(GroupName = "Aspect1",OrderIndex = 10),TestHelloAfter1(GroupName = "Aspect1",OrderIndex = 10),TestHelloAfterReturn1(GroupName = "Aspect1",OrderIndex = 10),TestHelloAfterThrows1(GroupName = "Aspect1",OrderIndex = 10)][TestHelloAround2(GroupName = "Aspect2",OrderIndex = 1),TestHelloBefore2(GroupName = "Aspect2",OrderIndex = 1),TestHelloAfter2(GroupName = "Aspect2",OrderIndex = 1),TestHelloAfterReturn2(GroupName = "Aspect2",OrderIndex = 1),TestHelloAfterThrows2(GroupName = "Aspect2",OrderIndex = 1)]public virtual void SayGroup(){Console.WriteLine("SayGroup");}}
    

    如上面的代码在目标方法上打了2组 那么对应的执行顺序是:

    1. 先执行TestHelloAround2 打印 “TestHelloAround2 start” 然后执行到里面的_next(aspectContext)会触发下面

    2. 执行TestHelloBefore2 打印 “TestHelloBefore2” 然后进入到

    3. 执行TestHelloAround1 打印 “TestHelloAround1 start” 然后执行到里面的 _next(aspectContext)会触发下面

    4. 执行TestHelloBefore1 打印 “TestHelloBefore1”

    5. 执行目标方法 SayGroup 打印 “SayGroup”

    6. TestHelloAround1运行结束 打印 “TestHelloAround1 end”

    7. 执行 TestHelloAfter1 打印 “TestHelloAfter1”

    8. 执行 TestHelloAfterReturn1 打印 “TestHelloAfterReturn1”

    9. TestHelloAround2运行结束 打印 “TestHelloAround2 end”

    10. 执行 TestHelloAfter2 打印 “TestHelloAfter2”

    11. 执行 TestHelloAfterReturn2 打印 “TestHelloAfterReturn2”

      执行的顺序如下图

    2. 面向切面编程

    上面介绍了利用Aspect标签来完成拦截器功能

    Aspect是一对一的方式,我想要某个class开启拦截器功能我需要针对每个class去配置。

    比如说 我有2个 controller 每个controller都有2个action方法,

    [Component]public class ProductController{public virtual string GetProduct(string productId){return "GetProduct:" + productId;}public virtual string UpdateProduct(string productId){return "UpdateProduct:" + productId;}}[Component]public class UserController{public virtual string GetUser(string userId){return "GetUser:" + userId;}public virtual string DeleteUser(string userId){return "DeleteUser:" + userId;}}
    

    如果我需要这2个controller的action方法都在执行方法前打log 在方法执行后打log
    按照上一节Aspect的话 我需要每个controller都要配置。如果我有100个controller的话我就需要配置100次,这样我觉得太麻烦了。所以我参考了Spring的Pointcut切面编程的方式实现了,下面看如何用Pointcut的方式方便的配置一种切面去适用于N个对象。

    定义一个切面:创建一个class 上面打上Pointcut的标签 如下:

    Pointcut标签类有如下属性:

    属性名说明
    Name名称Pointcut切面的名称(默认为空,和拦截方法进行匹配,参考下面说明)
    RetType匹配目标类的方法的返回类型(默认是%)
    NameSpace匹配目标类的namespace(默认是%)
    ClassName匹配目标类的类名称(和下面的AttributeType参数二选一必填)
    AttributeType匹配特定的标签(和上面的ClassName参数二选一必填)
    MethodName匹配目标类的方法名称(默认是%)

    切面如何匹配

    // *Controller 代表匹配 只要是Controller结尾的类都能匹配// Get* 代表上面匹配成功的类下 所以是Get打头的方法都能匹配[Pointcut(Class = "*Controller",Method = "Get*")]public class LoggerPointCut{}
    
    // *Controller 代表匹配 只要是Controller结尾的类都能匹配// Get* 代表上面匹配成功的类下 所以是Get打头的方法都能匹配[Pointcut(ClassName = "*Controller",MethodName = "Get*")]public class LoggerPointCut{}
    

    定义好了一个Pointcut切面后 需要定义这个切面的拦截方法(也叫切入点)

    配合Pointcut切面标签,可以在打了这个标签的class下定义拦截方法,
    在方法上得打上特定的标签,有如下几种:

    切入点说明
    Before标签在匹配成功的类的方法执行前执行
    After标签在匹配成功的类的方法执行后执行(不管目标方法成功还是失败)
    AfterReturn标签在匹配成功的类的方法执行后执行(只是目标方法成功)
    AfterThrows标签在匹配成功的类的方法执行后执行(只是目标方法抛异常时)
    Around标签环绕目标方法,承接了匹配成功的类的方法的执行权

    以上3种标签有一个可选的参数:Name (默认为空,可以和Pointcut的Name进行mapping)

    • 因为一个class上可以打多个Pointcut切面,一个Pointcut切面可以根据name去匹配对应拦截方法

    切入点标签所在方法的参数说明:

    • Around切入点 必须要指定 AspectContext类型 和 AspectDelegate类型的2个参数,且返回类型要是Task 否则会报错

    • 除了Around切入点以外其他的切入点的返回值只能是Task或者Void 否则会报错

    • 除了Around切入点以外其他的切入点可以指定 AspectContext类型 参数注入进来

    • After切入点 可以指定Returing参数,可以把目标方法的返回注入进来,如果目标方法抛异常则是异常本身

    • AfterReturn切入点 可以指定Returing参数,可以把目标方法的返回注入进来

    • AfterThrows切入点 可以指定 Throwing参数,可以把目标方法抛出的异常注入进来

    • 只要你参数类型是你注册到DI容器,运行时会自动从DI容器把类型注入进来

    • 可以使用Autowired,Value标签来修饰参数

        /// <summary>/// 第一组切面/// </summary>[Pointcut(NameSpace = "Autofac.Annotation.Test.test6",Class = "Pointcut*",OrderIndex = 1)]public class PointcutTest1{[Around]public async Task Around(AspectContext context,AspectDelegate next){Console.WriteLine("PointcutTest1.Around-start");await next(context);Console.WriteLine("PointcutTest1.Around-end");}[Before]public void Before(){Console.WriteLine("PointcutTest1.Before");}[After]public void After(){Console.WriteLine("PointcutTest1.After");}[AfterReturn(Returing = "value1")]public void AfterReturn(object value1){Console.WriteLine("PointcutTest1.AfterReturn");}[AfterThrows(Throwing = "ex1")]public void Throwing(Exception ex1){Console.WriteLine("PointcutTest1.Throwing");}}
    
    /// <summary>/// 第二组切面/// </summary>[Pointcut(NameSpace = "Autofac.Annotation.Test.test6",Class = "Pointcut*",OrderIndex = 0)]public class PointcutTest2{[Around]public async Task Around(AspectContext context,AspectDelegate next){Console.WriteLine("PointcutTest2.Around-start");await next(context);Console.WriteLine("PointcutTest2.Around-end");}[Before]public void Before(){Console.WriteLine("PointcutTest2.Before");}[After]public void After(){Console.WriteLine("PointcutTest2.After");}[AfterReturn(Returing = "value")]public void AfterReturn(object value){Console.WriteLine("PointcutTest2.AfterReturn");}[AfterThrows(Throwing = "ex")]public void Throwing(Exception ex){Console.WriteLine("PointcutTest2.Throwing");}}
    
        [Component]public class Pointcut1Controller{//正常casepublic virtual void TestSuccess(){Console.WriteLine("Pointcut1Controller.TestSuccess");}//异常casepublic virtual void TestThrow(){Console.WriteLine("Pointcut1Controller.TestThrow");throw new ArgumentException("ddd");}}[Component]public class Pointcut2Controller{//正常casepublic virtual void TestSuccess(){Console.WriteLine("Pointcut1Controller.TestSuccess");}//异常casepublic virtual void TestThrow(){Console.WriteLine("Pointcut1Controller.TestThrow");throw new ArgumentException("ddd");}}
    

    按照上面的配置

    • Pointcut1Controller.TestSuccess 和 TestThrow 2个方法 会被匹配

    • Pointcut2Controller.TestThrow 和 TestThrow 2个方法 会被匹配

    执行顺序

    单个切面顺序如下图

    多个切面执行的顺序如下图

    关于顺序是和上面用Aspect是一致的,只不过是1:N,1个切面来切N个目标

    切面功能与Spring相比缺少了一个灵活的切点表达式,所以功能会弱很多,这块目前我还没有很好的设计思路,欢迎来教育!

    3. BeanPostProcessor的设计

    参考Spring框架,
    在类的初始化过程中进行自定义逻辑而设计的BeanPostProcessor,有2个方法:

    • PostProcessBeforeInitialization

    • PostProcessAfterInitialization

    1. PostProcessBeforeInitialization

    该方法在bean实例化完毕(且已经注入完毕),属性设置或自定义init方法执行之前执行!

    2. PostProcessAfterInitialization

    该方法在bean实例化完毕(且已经注入完毕),在属性设置或自定义init方法执行之后

    一个使用场景例子:自定义一个注解来封装自定义逻辑

    先定义一个自定义注解

    /// <summary>
    /// 測試自己實現一個自定義註解
    /// </summary>
    [AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
    public sealed class Soa : Attribute
    {/// <summary>/// 构造函数/// </summary>public Soa(Type type){Type = type;}/// <summary>/// 注册的类型/// </summary>internal Type Type { get; set; }
    }
    

    这个注解的名字叫Soa,然后有一个构造方法,传参为一个Class Type

    下面需要实现一个BeanPostProcessor

    
    [Component]
    public class SoaProcessor : BeanPostProcessor
    {//在实例化后且属性设值之前执行public object PostProcessBeforeInitialization(object bean){Type type = bean.GetType();找到bean下所有的字段var fieldInfos = type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic);foreach (var field in fieldInfos){//看字段上面有没有打Soa自定义注解var soaAnnotation = field.GetCustomAttribute(typeof(Soa)) as Soa;if (soaAnnotation == null){continue;}//有的话根据注解的参数Type来实例化对象并设值var instance = Activator.CreateInstance(soaAnnotation.Type) as ISoa;if (instance == null){continue;}field.SetValue(bean, instance);}return bean;}//不管返回public object PostProcessAfterInitialization(object bean){return bean;}
    }
    

    好了,实现一个BeanPostProcessor就是写一个类继承并实现它的接口即可。
    然后打上[Compoment]注册到容器中即可。

    下面测试效果

    
    [Component]
    public class Test11Models1
    {[Soa(typeof(SoaTest1))] private ISoa Soa1;[Soa(typeof(SoaTest2))] private ISoa Soa2;public string getSoa1(){return Soa1.say();}public string getSoa2(){return Soa2.say();}
    }public interface ISoa
    {string say();
    }public class SoaTest1 : ISoa
    {public string say(){return nameof(SoaTest1);}
    }public class SoaTest2 : ISoa
    {public string say(){return nameof(SoaTest2);}
    }
    

    单元测试一下

    
    [Fact]
    public void Test1()
    {var builder = new ContainerBuilder();builder.RegisterSpring(r => r.RegisterAssembly(typeof(TestBeanPostProcessor).Assembly));var container = builder.Build();var isRegisterd = container.TryResolve(out Test11Models1 model1);Assert.True(isRegisterd);Assert.Equal("SoaTest1",model1.getSoa1());Assert.Equal("SoaTest2",model1.getSoa2());
    }
    

    Test11Models1这个类打了[Compoment]注册到容器,当从容器获取它的时候会走到上面的SoaProcessor。然后识别到里面有打了自定义注解[Soa],并根据注册的参数实例化。


    Spring是一个非常庞大的框架,里面包含了非常多的细节,比如处理依赖循环,单例如何Autowired多例,FactoryBean,代理类的生成以及兼容async await,新出的valueTask的方法代理等等,这个项目是我2018年开始写的,多次重构,每次重构也是反映对spring源码的理解程度不一样;这个过程非常有趣(一次次推翻我自以为看了源码就‘懂了’spring),目前最新版4.0.4 基本上包含了常用的spring功能,还会不断更新(看我是否越来越‘懂’spring),感兴趣可以看看单元测试


    我是正东,学的越多不知道也越多。如果决定去深究一个东西, 一定要完全搞懂, 并认真总结一篇博客让以后能在短时间拾起来 ( 因为不搞懂你很难写一篇半年后还能理解的博客 )

    欢迎白嫖点赞!

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

下班以后看什么,决定你人生的高度

全世界只有3.14 % 的人关注了青少年数学之旅王小波说&#xff1a;我活在世上&#xff0c;无非想要明白些道理&#xff0c;遇见些有趣的事&#xff0c;倘能如我所愿&#xff0c;我的一生就算成功。你的圈子将决定你的人生。每一位对事物都有着独特的态度让你成为一个有趣的人。今…

Mahout的taste推荐系统里的几种Recommender分析

Taste简介 看自:http://blog.csdn.net/zhoubl668/article/details/13297583Mahout 是apache下的一个java语言的开源大数据机器学习项目&#xff0c;与其他机器学习项目不同的是&#xff0c;它的算法多数是mapreduce方式写的&#xff0c;可以在hadoop上运行&#xff0c;并行化处…

bytecode java_Java 字节码解读

一、源代码public classPeople {privateString name;private intage;}二、利用Javap 反编译查看字节码结构信息javap -v People.class结果如下Classfile /D:/work/byte-code/src/People.classLast modified2020-12-9; size 240bytesMD5 checksum 54b8c1ad94a9c9cf5074fd8520454…

他狂骗五千万美元消失17年...却被一个纪录片导演锲而不舍的追到了镜头前!...

全世界只有3.14 % 的人关注了青少年数学之旅他&#xff0c;曾经是个做啥都无师自通的天才。他&#xff0c;在众多的行业里&#xff0c;莫名进入到了艺术品行业&#xff0c;没过多久&#xff0c;他靠着惊人的自学能力&#xff0c;成了艺术圈里所有人津津乐道的画商。他&#xff…

合肥.NET俱乐部第二期技术沙龙活动预告

各位亲爱的.NET从业和爱好者们&#xff1a; 大家好&#xff0c;自从19年举办.NET俱乐部第一期技术沙龙后&#xff0c;.NET在开源以及跨平台的加持下继续飞速发展&#xff0c;各种开源项目不断涌现&#xff0c;各种社区活动持续开展&#xff0c;.NET的生态逐步丰富&#xff0…

你见过扇贝游泳吗? | 今日趣图

全世界只有3.14 % 的人关注了青少年数学之旅正常颈椎VS颈椎病患者的颈椎丁香医生小学入学测试题你知道答案吗&#xff1f;图源网络为什么我的假期这么短&#xff1f;文丁香医生五子棋必胜秘籍图源网络那些女生称呼的真相图沙县小吃你有见过扇贝游泳吗小迷妹神吐槽

实名羡慕,国内这些厂.NET薪资高的吓人!

小米招C#工程师&#xff0c;要求WPF自动化设备&#xff0c;20~40k * 14薪&#xff1b;小鹏招WPF/.NET/PLC&#xff0c;25~40k年终&#xff1b;特斯拉招自动化控制16~25k * 15薪&#xff0c;华为应届硕士C#运动控制&#xff0c;就已经17~25k *14薪了。这些招聘让人眼热&#xff…

修车工在生命最后,才知道自己的儿子是如今世界首富!

全世界只有3.14 % 的人关注了青少年数学之旅在美国亚利桑那州凤凰城&#xff0c;曾经住着一个叫Ted Jorgensen的自行车修理工&#xff0c;他开了一家自行车店&#xff0c;平时默默的卖车修车&#xff0c;日子过的平平淡淡。在凤凰城郊区&#xff0c;他和二婚妻子住在这么一间不…

Sublime Text 2 中运行 PHP

2019独角兽企业重金招聘Python工程师标准>>> Sublime Text 2 has the concept of build systems. This basically means that if you are editing a Python file then you can run the Python interpreter on the source file your are editing and see the output …

python退出帮助系统help应该使用exit_python--help - tesion

python help学习python的过程中&#xff0c;难免遇到一些生疏的东西&#xff0c;为此需要参看相关的文档说明。Linux下众所周知有man可以查找系统的命令帮助页&#xff1b;对于python也提供了自己的帮助文档的2种方式&#xff1a;方式一&#xff1a;启动python解释器&#xff0…

试用GitHub Copilot一周后,我给你的建议是:不要使用它

如果你还不知道“GitHub Copilot”&#xff0c;它是GitHub推出“AI程序员”插件&#xff0c;可以根据你在VS Code输入的部分代码或注释&#xff0c;自动生成完整的代码。在我上次的文章中&#xff0c;演示了GitHub Copilot是如何工作的。刚开始&#xff0c;我对能够试用GitHub …

你知道长颈鹿是怎么睡觉的吗? | 今日趣图

全世界只有3.14 % 的人关注了青少年数学之旅一图教你打太极拳其实指甲也是一味药材你知道长颈鹿是怎么睡觉的吗&#xff1f;健身和肥宅的区别Win10的内置桌面并不是P的而是搭实景拍出来的当你玩游戏的时候...这才是第一个用蚂蚁花“被”...呗的人一般人的身高和他的臂展差不多一…

华仔andylau

好久没关注华仔&#xff0c;今天到华仔天地会逛下&#xff0c;转几张图片

ubuntu下搭建java web开发环境的详细步骤

系统环境&#xff1a;Ubuntu 10.10&#xff08;linux-kernel 2.6.35-22&#xff09;安装版本&#xff1a;apache-tomcat-7.0.29.tar.gz&#xff08;官方网址&#xff1a;Apache Tomcat&#xff09;安装步骤&#xff1a;1、下载 Tomcat下载 apache-tomcat-7.0.29.tar.gz&#xf…

C#网络类智能开关控制板实例

网络类智能开关控制板采用 TCP/IP 协议与控制主机&#xff08;PC 机&#xff09;进行通讯&#xff0c;运行稳定可靠&#xff0c;传输距离远&#xff0c;尤其适合不方便布线的场所。每块开关板分配一个唯一的 IP 地址&#xff0c;通过 IP 地址进行控制数据的传输区分&#xff1b…

扎哈遗作:北京大兴机场,耗资800亿,被英国《卫报》评为新世界七大奇迹!...

全世界只有3.14 % 的人关注了青少年数学之旅在经历了7次综合模拟演练、3场验证试飞之后&#xff0c;北京大兴国际机场终于迎来它“凤凰展翅”的高光时刻。这也意味着&#xff0c;北京将成为世界首个拥有双国际枢纽机场的城市。就连张口闭口说我们吃不起茶叶蛋、买不起榨菜的湾湾…

MVP on Board 没用小技巧

七月入选了微软 MVP&#xff0c;本文记录 on board 过程中遇到的小问题和没用小技巧。MVP Portal 当你收到来自微软的确认邮件之后&#xff0c;你将正式被接纳为微软现任 MVP 的一员。从此刻开始&#xff0c;你便拥有了 MVP portal 上相关的操作权限。MVP portal 的地址是 http…

普通人改变命运最关键的这几种方法

全世界只有3.14 % 的人关注了青少年数学之旅在工作之余&#xff0c;我们大量的碎片时间被手机占据。无意识的刷手机打发无聊&#xff0c;不如有趣又高品质的积累。我们特意精选了在不同领域的几个高品质公众号代表&#xff0c;希望让你在快乐打发闲暇时光的同时&#xff0c;也能…

JavaScript Dom编程艺术

当我对JavaScript还停留在只认识这几个字母的时候&#xff0c;有一天我突然心血来潮&#xff0c;在网上下了DOM Scripting的样章&#xff0c;照着里面的例子写了我平生第一个能让我知所以然JavaScript&#xff0c;在浏览器运行成功&#xff0c;兴奋不已&#xff0c;从此能把学习…

这批.Net程序员水平不行啊!居然ASP.NET Core Middleware都不会用

最近问了几个面试同一个问题&#xff1a;如果有多个自定义Middleware&#xff0c;如何控制它们的执行顺序&#xff08;比如先判断用户合法再写访问日志&#xff09;。居然大部分人答不上来&#xff1f;&#xff01; 对此&#xff0c;你有什么看法&#xff1f;ASP.NET Core Midd…