菜鸟的进阶--手写一个微型Spring

前言

想干嘛


  • 深入了解spring原理,特别是IOC容器是如何实现的?AOP是如何实现的?

  • 手写一个spring迷你版框架,实现容器和AOP机制。

我为什么想这么做


  • spring是整个java体系中最重要的框架,它整合第三方技术,将所有的技术可以融合在一起协同工作。

  • 想要真正了解spring最核心的两个概念:IOC & AOP

  • 想要通过手写一个简单的spring框架学习设计模式的思想。

完成度


总结一下本框架完成的功能:

IOC容器

  • beanFactory

  • 作为最重要的组件,充当着spring运行过程中核心中间人的身份。

  • 主要为它完成了:

  • 通过各种姿势获取bean。

  • 存储各式各样的bean实体对象,bean的描述信息等等。

  • 本框架中,主要完成了DefaultListableBeanFactory

  • ApplicationContext

  • 此容器是与beanFactory同级别重要的组件。它最重要的方法是refresh()方法。此方法会真正启动spring,它会注册相当多的处理器来对bean做处理。

  • 在此容器上,应用了模板方法的设计模式。通过分离出bean的生命周期各个阶段,通过注册bean处理器的方式来为它们做相关操作。

  • 本框架中,完成了AbstractApplicationContextAnnotationConfigApplicationContext 前者是一个父类,主要将bean的生命周期各阶段抽象出来,交由具体的实现类去做。例如像AnnotationConfigApplicationContext就是专门处理通过注解完成的bean注册,依赖注入等操作。类似的还有通过xml配置文件来处理配置文件内的bean,不过本框架中仅完成了AnnotationConfigApplicationContext。

  • BeanPostProcessor/ BeanFactoryPostProcessor

  • 后处理器分为了bean和beanFactory,前者是对单个bean做操作;后者是对整个工厂内的Bean做操作。

  • 后处理器是spring中真正"干活"的组件,它完成的主要工作有: 扫描包下的bean(工厂后处理器), 对bean的生命周期各阶段进行操作(bean后处理器)

  • 完成的处理器主要有:

  • 工厂后处理器:

  • ConfigurationClassPostProcessor

  • 主要完成对类文件的扫描,对相关注解的扫描,例如@Configuration,@Component等。

  • bean后处理器:

  • AutowiredAnnotationBeanPostProcessor

  • 根据名字也猜得出来,此处理器主要处理@Autowired注解的依赖注入,以及对于@Value的解析以及注入。

  • CommonAnnotationBeanPostProcessor

  • 此处理器主要处理@Resource注解的依赖注入,@PostConstruct 注解的初始化方法,@PreDestroy注解的销毁前方法。

  • LazyInjectBeanPostProcessor

  • 此处理器主要解决单例bean对多例bean的依赖注入时懒加载的问题。

  • AspectBeansInitBeanPostProcessor

  • 此处理器主要解决当依赖注入的对象是代理对象时,对其从sourceBean替换为代理bean的问题。

  • AnnotationAwareAspectJAutoProxyCreator

  • 此处理器主要处理AOP机制的相关操作。例如生成代理对象。

AOP机制

  • 本框架的AOP中,主要完成了通过注解方式完成对于切面类的织入。

  • 目前已完成的注解有: @Aspect ,@Pointcut,@Aroud,@Before,@After

  • 切点表达式的解析目前完成了三种: execution,@annotation,within

  • 通过AnnotationAwareAspectJAutoProxyCreator这个处理器来完成所有代理对象的生成。注意,此时虽然生成了代理对象,但是我并没有在这个处理器中完成对其的替换,因为仅仅是解析切点表达式,解析各个注解就已经让这个类特别的"胖"了。并且也是考虑程序设计时的单一职责原则。

  • 生成代理对象的方式采用了cglib。原先也是计划使用jdk动态代理的,但是后面遇到了一个无法解决的问题: 那就是对于依赖注入的对象是代理对象时,无法通过filed.set()进行反射注入,因为jdk动态代理生成的对象类型为Proxy$... 我去测试spring是怎么处理的适合,发现它也没有办法。。当我强制使用jdk动态代理,并且对被依赖注入的对象做增强时,它报了这样的错:


Description:

The bean 'testBean1' could not be injected because it is a JDK dynamic proxy

  • 最后我干脆就不用jdk的动态代理了,而是全部采用了cglib。

  • 通过上一步生成了代理对象之后,就可以通过AspectBeansInitBeanPostProcessor这个bean处理器完成对代理对象的替换啦。

遇到的问题


  • 纠结对于应该先实例化再进入生命周期的处理, 还是不实例化直接进入生命周期的处理

  • 如果先实例化,那么对bean生命周期的控制性就比较差。

  • 如果不预先实例化,那么依赖注入的顺序问题难以解决。

  • 最终还是选择了预先实例化,工厂后处理器负责注册@Component等注解的Bean,实例化的动作交给了context容器。

  • 通过autowired 后处理器完成对${}的解析,并且将值注入进对应的注解上面的value里面。但是,通过ImportAwareBeanPostProcessor 完成注入的时候,发现取得的method不是同一个(虽然class对象是同一个)。导致前面做的注解注入value,到ImportAwareBeanPostProcessor 的时候仍然是 ${xxx} ,后面通过将待处理的method加入一个集合结局这个问题。

  • 单例bean注入多例bean的时候,出现注入为null的情况。

  • 解决:

  • 参考spring的处理手段,增加一个@Lazy注解,专门处理注入多例bean.

  • 增加一个bean后处理器里专门处理这一情况。

  • 解析切点表达式,获取目标方法。由于目标方法可能存在多个,并且还有可能存在于不同的类中。所以需要考虑一个合适的数据结构来进行解析后的存储。这里我使用map,以class为Key methods为value.封装于poinCutDefinition中。

  • 如何处理pointCut注解,before..注解。如果它们的value不是目标方法,那么该去pointcut里面找。

  • 需要对这些注解信息做排序,将包含真正全类名的排在前面。

  • 得到了目标方法,就可以考虑增强的操作了。需要将增强方法织入到目标方法去。

  • 织入之前需要判断目标bean是否实现了接口,如果实现了接口统一使用jdk代理。否则使用cglib代理。

  • 还需要考虑before,after,around 它们对原方法的invoke顺序是有差别的。其中around的最不一样。

  • 这里需要考虑beanfactory里的bean有可能被替换的问题。(再次定义一个map用于存储aspect的bean)

  • 在这里获取到代理对象之后,并没有马上返回,而是留给了最后的bean后处理器。让它将aspectBean做最终的返回。

  • 需要考虑依赖注入之后的bean代理失效问题。主要是由于依赖注入拿到的bean和代理生成的bean不统一。

  • 先生成代理对象 还是先依赖注入?

  • 先生成代理对象,再依赖注入:

  • 代理对象生成之后,无法对该对象的原来的field成员变量赋值,(cglib代理对象没有拿到它的成员变量。)

  • 先注入,再生成代理对象:

  • 无法从已有对象生成,而是直接create一个新的。它的原来的成员变量需要通过get来获取。

  • 最终敲定: 先依赖注入,再生成代理对象。到最后的bean处理器的时候,专门处理将原先被依赖注入的bean替换成生成的代理对象。

  • 使用jdk动态代理生成对象,无法设置为依赖注入的bean,因为其类型为proxy类型。而被注入的Bean有可能是xxService类型。

  • 无法解决该问题。

  • 最终我自己测试了一下spring,发现spring用的也是cglib动态代理。

  • 报错如下:


APPLICATION FAILED TO START


Description:

The bean 'testBean1' could not be injected because it is a JDK dynamic proxy

The bean is of type 'com.sun.proxy.$Proxy51' and implements: com.markyao.springbootsource.bean.Tb1 org.springframework.aop.SpringProxy org.springframework.aop.framework.Advised org.springframework.core.DecoratingProxy

Expected a bean of type 'com.markyao.springbootsource.bean.TestBean1' which implements: com.markyao.springbootsource.bean.Tb1

Action:

Consider injecting the bean as one of its interfaces or forcing the use of CGLib-based proxies by setting proxyTargetClass=true on @EnableAsync and/or @EnableCaching.

  • 也就是说,依赖注入代理对象的情况下,只能使用cglib进行动态代理。

  • 最后

  • 不出意外的话,项目还存在很多bug,如果可以的话欢迎各位朋友玩一下,顺便帮我测试测试,如果有Bug可以及时通知我。

gitee

  • 目前已经将其上传至码云,感兴趣的朋友可以玩玩。

markyao-gitee

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
好的,让我来回答你关于 cocos2d-x 中 scrollView 的问题。 scrollView 是 cocos2d-x 中常用的 UI 控件之一,可以用来显示大量的内容,并支持滑动、缩放等功能。在 scrollView 中,可以添加多个子控件,比如 label、sprite 等,也可以添加自定义的控件。 在使用 scrollView 时,需要先创建一个 ScrollView 对象,然后设置它的属性,比如大小、位置、滑动方向等。接着,再往 scrollView 中添加子控件,最后将 scrollView 添加到场景中即可。 下面是一个简单的示例代码,演示如何创建一个垂直方向的 scrollView: ``` // 创建 scrollView 对象 auto scrollView = ScrollView::create(); scrollView->setContentSize(Size(200, 300)); scrollView->setDirection(ScrollView::Direction::VERTICAL); scrollView->setInnerContainerSize(Size(200, 800)); scrollView->setPosition(Vec2(100, 100)); // 往 scrollView 中添加子控件 for (int i = 0; i < 10; i++) { auto label = Label::createWithSystemFont("Label " + std::to_string(i), "Arial", 24); label->setPosition(Vec2(100, 700 - i * 70)); scrollView->addChild(label); } // 将 scrollView 添加到场景中 this->addChild(scrollView); ``` 在上面的代码中,我们首先创建了一个 scrollView 对象,设置了它的大小、位置和滑动方向等属性。然后,使用循环往 scrollView 中添加了 10 个 label,最后将 scrollView 添加到场景中。 通过以上代码,你可以学会如何使用 cocos2d-x 中的 scrollView 控件。希望对你有所帮助!
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值