说起Feign,那又是老东西了啊

我以前写Netfilx的时候就详细讲过Feign这玩意,其实用起来基本是一样的。

以前的文章地址 : 分布式微服务项目如何使用 Feign 实现声明式 REST 调用,以及自定义 Feign 配置

 

虽说切换到了Alibaba,但是也没啥变化,Feign该怎么用就怎么用,这就不详细说了。主要还是Sentinel

其实Sentinel 和Hystrix在代码里写起来也是一样的

首先肯定是要上pom.xml配置起来。加上对应的依赖。

 

接着写个Feign接口

调用的话就是这样子调用:

 

哎,观察我的NacosHelloFeign类,这里可以看到,我这用了一个fallback回退,这个回退指定的就是Sentinel 的实现,其实写起来和特么的Hystrix一模一样。不得不说SpringCloud这一点是真的做得好。

 

当然,配置肯定是得配置的,他也不会直接就给你用了。

Sentinel 虽说是适配了 Feign 组件。但默认是关闭的。需要在配置文件中配置打开它,在配置文件增加以下代码:

 

其实到现在,整个Feign的使用+熔断限流机制就已经配完了。

不过Sentinel 比起Hystrix真正优越的地方还没来呢。

那就是: 控制台

这个控制台功能丰富、UI好看,比Hystrix那是高到不知道哪里去了。

要是用控制台,又不得不下载代码、打包、编译、运行。

 

所幸,我们有神器docker。我在docker hub 上找了个镜像,直接用就可以了,镜像地址:https://hub.docker.com/r/bladex/sentinel-dashboard

直接执以下命令,将sentinel控制台起开绑到8858端口

 

然后自己的yml文件也得改一下,毕竟都上控制台了,也得把自己这个服务给注册进去。

全部的yml文件如下所示:

可以看到配置里有一个 sentinel.transport.port属性,这个属性的意思是在自己本体这个服务里边,在开个新的端口专门用来和 Sentinel 控制台交互

Sentinel 控制台自身也有,默认端口则是8719

 

到这里就差不多了,所有东西打开,然后就可以访问 http://ip:8858 进入Sentinel 控制台,默认账号密码都是sentinel

有一点还需要注意,那就是为了确保客户端有访问量,Sentinel 会在客户端首次调用的时候进行初始化,开始向控制台发送心跳包。意思就是说你一个服务注册进我这来,首先还是看不到的。

你得先经过别人的请求,我才会去监控你,所以在服务刚启动的时候进入Sentinel 控制台找不到自己的服务是很正常的,只要启动时没有报错就不会有问题。

 

 

阅读全文

 

中秋佳节当天,人在异乡,无法和家人团聚,既然如此 不如提升一波自己的姿势水平,免得浪费这假期大好光阴。

说道微服务,现在可不流行Netfilx那一套了, 现在都9102年9月了,Spring Cloud Aliabba都已经孵化完毕,再加上Netfilx将他旗下的项目通通打入冷宫,现在在不学习SpringCloudAlibaba体系就赶不上时代的潮流了。 Netfilx那套体系我都搭过demo 写过博客,各种配置啊注意点啊都挺详细的,感兴趣的可以翻我以前的文章。

 

 

https://github.com/alibaba/spring-cloud-alibaba 这是 SpringCloudAlibaba 的项目 git。

这篇文章主要是开个SpringCloudAliabba篇的头,正式开始SpringCloudAlibaba的学习。

 

 

废话不多说,进入正题。

今天就从微服务最重要的地方开始,当然,也只能从这个地方开始。

那就是服务治理。

 

在 Spring Cloud Netflix 使用的是Eureka,而SpringCloudAlibaba 给我们提供的就是Nacos。

而且,Nacos比Eureka 更加强大,他还可以用来当配置中心。

 

这里我就简单的先完成他基本的职责:服务注册与发现。

首先nacos和Eureka有点不同。

他这个应用。不是我们自己写的,Eureka这个东西他需要我们自己创建一个应用。然后在编写代码,然后再将项目打包,在启动。

Nacos这个东西他和Zipkin有点像,是那种直接下载下来运行就完事了的。

 

我这儿用Docker来运行一个单机的 Nacos 镜像添加到8848端口(默认端口),当然,集群是肯定可以集群的,我这先不玩。

对了,不知道为啥我用网易的docker镜像地址下载会卡住,之后换了alibaba的来下就没问题了。

nacos登陆地址:http://ip:8848/nacos   默认账号密码是nacos/nacos

 

开启Nacos服务完毕后,就要开始写服务提供者和服务消费者了。

其实这俩都差不多,反正注册进服务治理中心了,A可以调用B的服务,自然B也可以调用A的服务,导入的配置也一样,我这就以服务消费者来举例子,避免贴两份基本相同的代码了。

 

我这使用的是最新的版本,要用就用最新的,不然没意思。我之前写的Netflix项目也是使用的最新的,当时用的是SpringCloud F版第三版

现在这个项目用的是SpringCloud G版第三版,SpringBoot 2.1.8.RELEASE

 

先创个依赖项目进行最基本的统一依赖管理

 

这个项目集成了SpringBoot 然后导入了SpringCloud的依赖和SpringCloudAlibaba的依赖。

依赖项目写完之后就是正式开工编写服务。

这个服务就直接继承我刚刚写的依赖项目就可以了。

 

然后在其中,引入web、actuator、test几个SpringBoot依赖。当然还有必不可少的SpringCloudAlibaba Nacos 依赖。

启动类:

 

这个启动类用了个 @EnableDiscoveryClient 这个注解,该注解是SpringCloud内置的,Nacos实现了这个注解,导入了Nacos的依赖的话就代表要注册进Nacos里边。

这样的好处就是以后要是换个新的服务治理中心,代码都不用改就可以轻易的更换,完美符合里式替换原则

 

 

既然能注册进去了,那接着还是老规矩,来个RestTemplate进行远程服务的调用,@LoadBalanced代表负载均衡,这些东西我以前的文章都有讲过啊,这儿就不详细说了。

 

这里就是编写一个控制层,来进行接口对外暴露,内部就是使用RestTemplate+LoadBalancerClient 来进行负载均衡的调用其他服务。

 

当然,yml是肯定要写的。

 

对了,这个management.endpoints 下边端点一定得打开。

因为 SpringCloudAlibaba 的 Nacos 在实现的时候提供了一个 EndPoint, EndPoint 的访问地址为 http://ip:port/actuator/nacos-discovery。

这玩意和服务注册发现息息相关,要是Nacos访问不了这个端点,那你就注册不了。

 

其实到这就差不多了,服务已经可以注册进Nacos里了,然后微服务之间的互相调用也是没有问题的,负载均衡也可以实现。

都没啥好说的,只要学会SpringCloud的思想,组件从一套换成另一套也就几十分钟的时间。

阅读全文

地址:

https://github.com/skypyb/code_demo/tree/master/spring-security-demo

内附 SQL 脚本

 

标准的RBAC权限设计,基于动态查询数据库的权限判定(以接口为粒度,即Request URL+Request Method)、基于JWT的认证授权流程。

当然,以上都是在SpingSecurity下实现的。

 

不得不吐槽一句:

SpingSecurity 真的是 Spring 全家桶中的异端邪教,有丶难用,研究好一段时间看了不少别人写的博客,甚至还打断点看了一些源码才给他理解。

想用这个东西是要学习的 ! 完全无法达到开箱即用的效果。比起这玩意,Spring、SpringMVC、SpringBoot、SpringData 这些一小时就能上手的比他不知道高到哪里去了。

给他写了30+个类,从上周写到这周,提交了20+个 commit 才将基本的认证、授权流程写完

不愧是一堆设计模式狂魔写出来的东西,我佛了

 

 

阅读全文

 

2019/08/02  我在上家公司上完了最后一天班,成为了无业游民。

此时,我在长沙,这个从小长到大的地方。

 

2019/08/09 ,人在上海,刚下飞机,台风就来了。

除了刚下飞机的这几小时有点小激动外,之后都非常平静,换了个地方也完全没什么违和感、不适应之类的。

 

现在,2019/09/03,上完了完整的一周班,现在已在是我在上海入职的第二周多

投简历求职面试找工作的时间,总共为一周,12号-19号,然后收offer、体检、入职,一切都是那么自然。

 

 

先随便扯扯,说说在这互联网寒冬期,我为什么离职。

之前入职的公司规模比较小,50人左右。刚进的时候还是比较有激情的。

当时加入这家公司最主要的就是该公司用的技术算比较新的,虽然项目规模小,但是也是实际在生产中用上了SpringCloud全家桶。

工作也没啥不满意的,一年下来没感觉有什么不舒服。 朝九晚六一周五天的标准工时。加班也不多,偶尔有几次加到过8点。

工资虽然不多,但是在长沙还是够得,刨去租房(40平整租)和吃喝,一个月还能剩下3K。

 

那为啥离职呢?

主要还是感觉没意思了。其实早就有这个心思,但还是苟到了待满一年,免得简历不好看。

同事们有点菜,待着待着我觉得我在同事间就变成最厉害的了,也不是自我感觉良好,当别人比你菜的时候是很容易感觉出来的。而你看着别人扭曲的代码都不好说什么,别人工作经验还比你长。

上司我至今还没看懂是个什么水平,听说6/7年开发经验,然后自己出来和曾今的大学同学一起创业。但我看其写出的代码跟一坨 ○ 一样,可拓展性可维护性吊差,没比我同事高到哪去。可能是长久将重心放在产品上技术退步了吧。对了,上司就是公司总经理之一,至于leader、架构师、产品、项目经理之类的通通都不存在。

也不是说没挑战,在这家公司里什么 缓存、消息、多线程、分布式任务调度 我全都是深度参与,其中权限啊、消息啊、服务拆分之类的基本上全是我来搭的,遇到的坑解决的坑也不少,反正前后端、运维、测试全是开发干。

我是感觉这项目规模估摸是上不去了,在搞一堆花里胡哨的又有什么用,每天写的代码慢慢的没有成就感了,待着待着就变成了摸鱼怪,天天上 v2 吹吹比,然后带薪拉屎,上司来个需求我做完了不吱声,让我在别人眼中处于一个可能有活可能很闲的中间态,以便我随时切换。

 

还有,主要是身边的同事压根不学习,都是crud boy,我待着实在难受,这种只有自己一个人在进步的感觉挺操蛋的,压根也不知道自己成长到了什么地步。

我也不说我每天都比昨天厉害,这是大佬才能做到的。但是我基本上每个星期都会比上个星期更厉害,我休息时间一直都在看视频、看书、写代码。看我这博客就知道了,每个月都会写个几篇文章,这个博客+github大概能代表我一半的水平?因为毕竟不是所有知识都会记录上来的。写个博客太费时费力了;花一小时能写一篇算不错的,还不算写的博客内容的学习时间。

 

 

瞎扯扯完了,进入正题。

 

1、现在互联网寒冬,一线城市面试都面啥玩意?

2、从二线城市到一线城市,换城市发展,如何才能按部就班不慌不乱稳中带皮?

 

 

 

 

先从 1 开始,我这面一周都被问了啥?

 

1.1 、java核心类库方面

HashMap底层必问,无非就是数组链表红黑树,何时树化(反树化)、然后 1.7/1.8 区别(扩容机制、底层结构)、put()方法源码、rehash

ArrayList/LinkedList: 这个没有问很深的,但也会问,反正我只会1.7/1.8区别,底层数据结构和初始化大小这些东西。ArrayList初始化1.7/1.8是有区别的,注意就行了

反射/注解/代理:这个一般说下作用和使用场景就差不多了,一般是拿来切入框架原理的,框架下面再说。

多线程: 重点,还好我这块比较扎实,因为之前公司多线程场景非常多,导致我找过很多资料学习。

synchronizd: 放在普通方法上和放在静态方法上区别是啥?锁的是什么对象?还有面试官问我 synchronized 底层,我差点笑出声。我给他把偏向锁/轻量级锁/重量级锁 讲的明明白白,对象头MarkWork经历了什么变化,线程栈怎么CAS操作对象头,如何复制对象头,为什么,JIT编译器会如何优化你的synchronized。反正吹了大概有好几分钟。

volatile: 这个关键字是干嘛用的? 在哪种场景用到?底层怎么实现的? 这玩意我也给吹的明明白白的,无非还是JMM这些东西

ConcurrentHashMap: 问底层源码,为什么快,分段锁机制,1.7/1.8区别,这个我纯靠背没啥好说的

Lock接口: 主要是问 ReentrantLock ,问我ReentrantLock和synchronized区别是什么,Condition怎么用,然后还有底层,对,这玩意也问底层我佛了,我就答了是用的AQS,CLH队列,具体怎么实现的我没正面回答混过去了,讲的不大好没怎么准备

pool: 线程池七种参数都是什么作用?底层是怎么实现的,根据参数的不同 execute()/submit() 时内部如何处理? 这个很简单,没啥好说的,随便准备一下就差不多了。

Queue/JUC工具:   没人问,亏我准备好久

JVM:  虽说我不知道问这个到底有什么意义,但是我基本都答的还行

基本上就是这几个:GC算法?内存管理? 对象啥时候进入老年代? java提供的命令?OOME怎么定位?7种垃圾收集器都是哪些?有什么用?对象如何被清理(可达性分析、二次标记,F-Queue)?JMM解决什么(然后引出happens-before、volatile,或者反向)?

上边这几个我全部都会,就7种垃圾收集器都是哪些,有什么用没记那么多就答了一点点,类加载没人问我,也没问更深了。还好我看过那本JVM神书

 

1.2、java框架

SpringMVC:   MVC设计思想? 请求整体流程? 这个一般就问一下过一下,还没有问我底层源码的。从DispatcherServlet –> return ModelAndView 这个流程说清就差不多了。

Spring:  这个就是万恶之源,啥东西都能被Spring引出来, 然后大家又都很熟,基本的东西压根不问你,问的全是底层源码/设计/思想,还好,偏偏我就看了相当一部分Spring底层源码,设计模式也实操的不少。

为什么要用Spring?带来了什么好处?

用了哪些设计模式? (然后就从你的回答中引出 你实际用过哪些设计模式?  然后你就得答你在原来的项目中是怎么用的)

然后就是Spring里策略、工厂、代理、单例都是怎么用的,你一说单例那就是就是单例如何保证线程安全呢?

然后根据你的回答再下一步(静态内部类 —> JVM类加载机制) (double check  –> volatile关键字)

你说工厂/策略,那工厂模式是在哪用?(BeanFactory) 策略?(Bean实例化)  然后就开始问细节

然后你说就是代理,哪用了代理? 有哪几种代理? AOP 用的什么?  AOP 然后就自然而然引出事务,然后就是事务实现?传播机制?

还有个比较过分的,让我说Bean出生到死亡整体流程。这玩意就挺恶心了,说个三五分钟压根不是什么事。

然后就是Spring植入的一些钩子作用,别的框架是如何利用的Spring提供的钩子,AOP是怎么用的Spring的钩子?

SpringCloud: 网飞全家桶,这个一般不问底层,主要是会问一些思想。还有组件有哪些,怎么用?分别在什么环境中用?微服务为了解决什么问题,又带来了什么麻烦。只要真的用过应该没问题的。无非就是这么几个

服务这么多  如何访问

服务这么多  如何通信

服务这么多 如何管理

服务挂了 怎么办

解决方案拿出来就完事了。

 

ORM : 我说我之前公司用的是JPA,然后他们就没问我了,就问了下我会不会MyBatis,我说会啊,使用肯定没问题,我用JPA又不是不写SQL,就没问我别的了。

 

1.3、中间件(数据库、缓存、消息)

主要还是问MySQL比较多啊:

MySQL索引?索引原理?画出来?(我没画出来,这也太过分了)

事务隔离级别?脏读幻毒不可读? 基础知识,秒了

InnoDB和MyISAM区别?使用场景?表级锁行级锁区别?乐观锁如何实现? 也是基本知识,都没啥好说的

然后就是手写SQL: 我面的好几家都是有面试题的,SQL肯定是有,偏偏还都是这种很类似的表(学生、课程、分数)

主要就是一些分组聚合操作,统计平均值、不及格人数、大于80分的人数、排序, 一般来说就是表连接一下,然后 group by + having + order by 几个子句一把梭,最后在SELECT子句后面在用几个函数(max()、avg())就差不多了

Redis的话有被问到这些,一般来说要配合自己项目讲

使用场景?为啥要用?是遇到了什么瓶颈?怎么用的?  该怎么说怎么说就行了。

如何实现分布式锁?有没有用到过?Redis实现的分布式锁会有什么问题?如何解决?

缓存穿透?(这个是这样问的,面试官问我有人脚本攻击你缓存不存在的数据怎么办)

至于底层实现的话没人问我,估计是看我经验也不多

RabbitMQ:

这个也是配合项目说

有什么用?(异步削峰解耦咯)

消息中间件解决了你们系统什么问题?怎么用的? 这个也是实际是怎样就怎么说就行了

rabbitmq的几种绑定方式?Exchange怎么转发消息的? 你在项目里具体是用哪几种?

至于死信和保证消息不丢失,都没人问,我是都准备了。

 

 

我简历上写了docker,基本每家公司都有问我,我只是自己捣鼓着玩而已,真没想到这玩意被问的挺频繁,我也没准备,就说下docker的基本操作。

然后还有linux基操、还有分布式事务。这些不在以上清单内的,我不知道该咋分。不好分类,就写这了

 

1.4、项目

其实这个没什么好说的,每个人做的项目都不同,在其中发挥出的作用也不同,我说了也没有参考意义,再说,说我离职公司的项目也不太好,好歹也是有保密协议的。

之所以还是摆在这,那是因为这个东西也是必问的,而且占了相当大的比重,需要好好的捋一捋之前自己写的功能才行。

 

 

写在这些面试经验的最后,说实话,我真没想到真的问的这么深,我还以为我的水平能超过大多数一年经验的人了。

一年经验都要会这么些东西吗?上海真的吓人。还好我这一年学的也多,想到这我不禁在思考,crud boy 现在来上海肯定很难受的,这要是3年经验,该问啥啊

 

2、从二线城市到一线城市,换城市发展,如何才能按部就班不慌不乱稳中带皮?

以我亲身经历来说。

 

1、最好准备 2w 元RMB,这个数字是比较稳的,主要是要苟过前面找工作的时间拿到入职后第一份工资。

2、准备面试,准备一个月巩固知识差不多了,学新知识肯定是来不及的,这个就看平日积累,该是什么水平就是什么水平,个把两个月提升不了多少

3、裸辞,这个没什么好说,换城市不存在骑驴找马的

4、裸辞的下周五,定为离开现城市的时间

5、裸辞之后的这一周,需要做这几件事情:

5.1、下载拉钩和BOSS,因为这两个是唯二可以上传附件简历的地方,我也投了58和智联,实在是没什么卵用。

5.2、裸辞后的这一周,投目标城市的简历,把面试安排在到达目标城市时间的下周,即周五到目标城市,周一开始面。要是有电话约你面试,先申请一下看是否先可以电面、视频面

5.3、下载租房软件、合租(有钱就随意了)  推荐zuber , 房源真实,真的好用!

5.4、约看房就完事了,约在到达目标城市的之后两天(周末),就选短租的。租2、3个月是最好。房租+押金这里可以轻松消耗掉近 10k(甚至更多)资金

5.5、开始进行面试,对,本地面试,找找手感,发现自己的不足,虽说很对不起别人公司

5.6、定好机票/火车票、 酒店

5.7、以上操作,同时进行

6、裸辞后的下周周五,到达了目标城市,此时先入住自己订的酒店

7、临时落脚点最重要,此时和之前约得房东/二房东/室友 见面看房,在周末两天内搞定房子

8、短租房子租好了,入住了,临时落脚点有了,周一就开始面试

9、一切准备完毕,进入面试流程,到面试流程就没什么好说的,白天面试晚上学习,没面试也学习,直到找到工作。

 

稳!反正我是一周就找到了。薪水相比原薪水 * 2.5 , 浮夸的一批,不过也算得上对得起我这一年努力,没什么觉得运气好或者捡到漏之类的,我面试时报的价都是这个数,还想着有个讨价还价的流程来着,没想到直接给了。本来想着要是一个月找不到就在降点,还是比较顺利的。

话说其中两个面试,轮番恶战,经历1轮笔试+4/5轮技术,都走到HR面前了,偏偏最后没给我offer,我都觉得稳得不行的。我觉得还是学历的锅好伐,真的太伤了(专科)

不过现在加入的公司,我觉得也挺不错,规模几千人,965工作制。我待的这一周来看并没什么加班,其中一天项目部门开了个会开到7点半,一群人回到办公室里,灯都被灭了

 

不过既然加入了大公司(我这里的大公司指的是人多的公司),那和以前的小公司的区别还是挺大的。

一个项目组,产品、UI、测试、实施、APP、小程序、后端、前端,全都有,这阵仗我是没体验过的。

然后就是规范,龟龟,我都被惊了,接口请求和响应都要定义专门的实体(REQ、RES)来传输,数据库查询出的是PO和DTO,其中DTO用于在控制层和服务层传输等等…我之前呆的公司都是数据库实体(PO)游离于各个层面..

每改个接口/新增接口都要写文档,说麻烦还真挺麻烦的。

需求方面,说实话,我才呆一周就感受到这方面的麻烦了。尼玛一个接口下来可能改n遍,产品,不愧是你.jpg

最后就是关于技术,我觉得…可能待在这种流程规范的公司在技术方面可能难以长进了…

因为我问了下同事,我问,你呆在这一年,干了些啥? 回答是:CRUD和复杂CRUD

我问:普通开发,能参与项目架构设计么、缓存、消息、多线程 普通开发能接触么?回答:没有,就是CRUD

WTF?我在之前的公司,那使用消息来进行任务调度的流程都是我一手搭的,缓存我也出了不少力,整体项目架构和流程我都是深度参与,UML图(我画的)我还保存着。多线程那更是基本上天天玩,这一下步入到无尽的CRUD之中去总感觉心情有点微妙…

 

 

 

 

 

 

 

 

阅读全文

 

 

万字长文警告 !建议在首页看的,点击标题进入文章页查看,好看点。

 

本文章又名: 兼容 Spring 体系的 java 框架实现妙计

 

 

前排先提示一波:   读我这篇文章可以没看过 MyBatis 源码,但是 Spring 源码最好是要看过的,因为很多东西我不会解释,没看过 Spring 源码的估摸着会有些懵逼。

 

看完这篇文章的话,基本上可以自己手写兼容Spring体系的框架了。

本文章大概能解答这些问题:

如何使用Spring提供的扫描器?

MyBatis 注解生成 mapper 的流程是怎样的?

通过注解选择配置的启用与否是如何实现的?

如何注入自己的动态代理对象到Spring IOC容器 (重点)

在注入自定义 BeanDefinition 到Spring IOC容器中时,FactoryBean 起到个什么作用?

 

 

 

先看效果,再谈实现

首先,我写的这个demo项目里只有一个dependency

 

然后,我有俩Dao接口,都提供了一个query()方法

 

最后,这是启动类和启动配置:

 

这是运行后的控制台打印:

——-> SELECT name FROM user WHERE id = 1
——-> SELECT name FROM user WHERE id = 2

 

这里先说明一下,JDBC的流程我是没写的,因为这个不是重点。我生成的动态代理对象只是单纯的解析了一下@Select注解 然后将其中的value值打印了出来而已。

 

 

如何实现 ?

首先,肯定是要实现这么个效果: 将自己自定义对象注入到Spring IOC 容器中。

但是Spring IOC容器内部,保存具体Bean实例的是 singletonObjects 这个ConcurrentHashMap (我这默认只说单例Bean啊)。而向这玩意里边塞值的方式 Spring 是没有提供给我们的,他只能通过Spring扫描Resouce资源然后解析为BeanDefinition再才能从getBean时解析BeanDefinition实例化对象放入此单例缓存中。

既然我们自己没有方法可以直接往单例缓存中塞值,那我们可以采取一点迂回战术。

想要实现注入这种效果,根据Spring IOC的加载Bean、使用Bean流程来说,提供了好几个不同的钩子方法给你自己实现然后在他整个Bean生命周期、IOC容器生命周期的不同阶段调用。

比如:  BeanFactoryPostProcessor

BeanFactoryPostProcessor: bean工厂的bean属性处理容器,主要是可以实现该接口从而管理我们的bean工厂内所有的BeanDefinition数据,可以随心所欲的修改属性。

BeanFactoryPostProcessor 的机制就相当于给了我们在 bean 实例化之前最后一次修改 BeanDefinition 的机会,我们可以利用这个机会对 BeanDefinition 来进行一些额外的操作,比如更改某些 bean 的一些属性,给某些 Bean 增加一些其他的信息等等操作。

BeanFactoryPostProcessor提供给我们实现的方法 postProcessBeanFactory() 中可以得到当前beanFactory的对象,然后通过该对象的 registerSingleton() 方法就可以注册一个Bean。

使用起来也很简单,比如我这样,就成功的往Spring IOC 容器中注册了一个name为”123asd”的String对象:

 

但是,用 BeanFactoryPostProcessor 不好。因为他是专门为了给你进行BeanDefinition的修改而生的,比如说你想改某个框架,就可能用到此类。

到这个钩子调用的阶段,BeanDefinition早就扫描完了,还没开始实例化。你在这强行注入已经实例化的对象到 singletonObject 里,用是可以用,但是违反了相应规范,也指不定会出什么未知bug。

 

我们最好还是能够在Spring的扫描阶段来注册BeanDefinition,保持和 Spring IOC 容器流程一致化。所幸Spring提供了专门的钩子用来给你注册BeanDefinition。只要有这个钩子,那就没问题了。

因为只要能够注入自己的BeanDefinition,在使用name获取对应的Bean时,要是获取不到,它就会试图获取此name对应的BeanDefinition,他只要一找到这玩意,就会开始实例化进程然后将其实例化,最后放入缓存后返回给调用者。

Spring提供的用来注册BeanDefinition的钩子有两个,一个是 BeanDefinitionRegistryPostProcessor,一个是 ImportBeanDefinitionRegistrar

BeanDefinitionRegistryPostProcessor也实现了BeanFactoryPostProcessor ,不过在实现此接口的时候可以无视BeanFactoryPostProcessor 提供的 postProcessBeanFactory()这个方法,一个类做一个事情,实现了BeanDefinitionRegistryPostProcessor就只用关心如何如何注册BeanDefinition就够了。

 

BeanDefinitionRegistryPostProcessor和ImportBeanDefinitionRegistrar的主要区别在于其提供给程序员实现的方法不同。

BeanDefinitionRegistryPostProcessor提供的方法仅带有一个BeanDefinitionRegistry参数 , 用于给你注册Bean。

而ImportBeanDefinitionRegistrar在其基础上,还额外带了一个参数 : AnnotationMetadata,此参数可以获取指定注解的属性

 

一般来说BeanDefinitionRegistryPostProcessor用来解析外部资源配置,ImportBeanDefinitionRegistrar解析注解配置。

以MyBatis来说,对应上边这俩的实现就是 :

MapperScannerConfigurer (实现BeanDefinitionRegistryPostProcessor)

MapperScannerRegistrar (实现 ImportBeanDefinitionRegistrar)

 

 

 

好,既然钩子有了,那么是不是就可以开始进行实例化了呢?

 

错!

 

首先,得用上这些钩子。咳咳,这不是在说废话啊!!

毕竟是一个写框架的流程,不是说想用就可以用的,不是说什么一个@Compent注解加上就完事了的。而是需要将配置摆在这里,用户需要的时候才进行配置加载。

如果是XML体系,那没啥好说的,用户就直接在 Spring 的 applicationContext.xml 配置里加载需要的配置Bean就行了。

那么问题来了,要是用注解呢?

 

只要用过Spring体系的,肯定用过一些Enable开头的注解,一般长这样: @EnableXXX  

只要在某个配置类或者说启动类上加入这个注解,就会自动加载某些框架的配置。比如什么 @EnableHystrix、@EnableEurekaServer、@EnableWebSecurity、@EnableCaching、@EnableAsync等等等等奇奇怪怪的注解。

这又是什么操作?

这个问题很好解决,还是以MyBatis举例,只要看一下MyBatis源码就知道这套东西是个什么意思了。

MyBatis的注解配置,有一个必要的类注解叫MapperScan,它主要是来定义扫描哪些包的。然后在该注解中导入了配置类进行加载。

 

看16行高亮行,导入的配置即为我上边说的实现了ImportBeanDefinitionRegistrar 接口的MapperScannerRegistrar。

关于Spring 的注解扫描机制,在使用注解加载配置时,Spring会对所有配置类上的注解进行扫描,也会对注解头上修饰的注解进行递归扫描。

@Import 注解 ,就属于一个Spring提供的特殊注解,扫描到该注解引用的类时,就会自动加载该类资源(也就是装载进Spring IOC容器),  一般来说就是拿来导入配置的。当然,导入普通的Bean也不是不行,不过用@Import来导入Bean就属于画蛇添足了。

导入配置的这个阶段,处于Spring IOC容器的扫描阶段。

 

其实像是这种 @EnableXXX  的注解,你点进他源码看,肯定是用了@Import 的,比如,我也这么定义了一个注解。

看我之前最开始贴出来的实现效果。是不是发现我用的 MainConfig.java 启动配置中,在类上注解了一个@EnableSkypyb 

这个 @EnableSkypyb  的底层我是这么写的

他导入了 SkypybRegistrar.class 这个配置,而这个配置,就是重中之重了。

因为,我是完全看着 MyBatis源码 写出的这个配置类,流程可以说和 MyBatis 的加载 mapper 流程一模一样。只是相对于MyBatis而言,少了很多判定和处理而已。

 

 

现在配置现在也能动态导入了

那么就可以进入扫描、修改BeanDefinition、实例化代理对象的流程了

 

核心配置代码:

 

逻辑清晰,看起来应该不困难。

理所当然的,为了使用@Import 导入实现了ImportBeanDefinitionRegistrar接口。同时还实现了一个ResourceLoaderAware 接口,其实实现这个ResourceLoaderAware 接口没什么鸟用,因为我当时是一直注入失败,所以MyBatis源码里那套东西我都试了一遍,他实现了ResourceLoaderAware 接口我也照葫芦画瓢实现的,实际上后来我全写完了后把这玩意删了也不影响。

 

SkypybRegistrar内部处理流程 :

1、先从 AnnotationMetadata 类中得到了我 @EnableSkypyb 注解内的的信息,主要是想知道,我应该扫描哪些包。

2、然后创建出自己自定义的扫描器,将 ImportBeanDefinitionRegistrar 提供的注册机 BeanDefinitionRegistry 传入进去

3、最后开始扫描,将需要扫描的包名数组enableSkypybValues 传入 doScan() 方法,扫描之前会先注册下扫描过滤器

 

就看这么个流程,肯定有点懵逼,因为到底是如何扫描出BeanDefinition的,又是如何将BeanDefinition改成我们自己定义的代理对象的,这里都没体现出来。

这个就必须要进入我自定义的 ClassPathSkypybScanner 这个类里了,不过我不会一股脑给他把代码全贴出来,不然可能会让人有点懵逼。

我接下来将以方法的粒度逐个方法进行讲解。探秘Spring IOC扫描器,以及我说的: FactoryBean 究极奥义

 

ClassPathSkypybScanner 类声明:

继承了ClassPathBeanDefinitionScanner ,只有一个构造方法,使用的是父类的构造器。

第一个参数是注册机,没什么好说的,第二个参数为是否使用默认过滤器,我传入false表示不使用。

 

关于ClassPathBeanDefinitionScanner :

ClassPathBeanDefinitionScanner作用就是将指定包下的类通过一定规则过滤后 将Class 信息包装成 BeanDefinition 的形式注册到IOC容器中。

可以继承他覆盖其方法,从而设置自定义的扫描器。实际上MyBatis就是这么做的,当然,我也是。

过滤器用来过滤从指定包下面查找到的 Class ,如果能通过过滤器,那么这个class 就会被转换成BeanDefinition 注册到容器。

这里我把它默认的过滤器禁掉,等会用自己写的过滤器

 

在我自己定义的自定义扫描器里边,最核心的就是 doScan() 方法了

这个直接贴代码:

 

流程也很简单:

1、直接调用父类(ClassPathBeanDefinitionScanner) 的 doScan() 方法,将我传入的包名中的类都扫描出来

2、ClassPathBeanDefinitionScanner 提供的 doScan() 在扫描后,会自动注册进 beanDefinitionMap

3、我获取了doScan()返回值后只需getBeanDefinition() 进行修改,反正指向的是一个堆内存地址

4、至于如何修改,之后再说,因为第一步调用父类 doScan() 方法有坑

 

ClassPathBeanDefinitionScanner的 doScan() 方法,你要是没做任何处理,他一个类都不会给你扫出来

需要你重写父类的 isCandidateComponent() 方法,在他扫描阶段,他会一个个走进这方法判断。

看其class是不是一个候选组件。只有是一个候选组件,才会进入到下一步判断是否通过过滤器

 

这是我重写的isCandidateComponent() 。只要是一个接口,并且是单独的,就判断其为一个候选组件。

 

至于我注册的过滤器,其实就是无脑返回true。

但是过滤器这一块是有门道的,很重要的。

我这只是为了演示,才自定义了一个过滤器让其全部通过。

addIncludeFilter() 传入一个TypeFilter,任何一个TypeFilter返回true就代表可以通过

TypeFilter接口目前有AnnotationTypeFilter实现类(类是否有注解修饰)、RegexPatternTypeFilter(类名是否满足正则表达式)等。

可以方便的实现只扫描出符合条件的类,比如 Spring 扫描 @Compone 修饰的类,他就是用的 AnnotationTypeFilter

 

只要这样写,调用父类的 doScan()  方法就可以成功扫描出指定的BeanDefinition了

然后,我在扫描完毕后,使用了自己自定义的方法 processBeanDefinitions() 对该BeanDefinition集合进行处理。

 

 

 

 

对,就到最关键的时机了!!!

总所周知,Spring IOC生命周期扫描出BeanDefinition后会放入beanDefinitionMap之中。

然后在 getBean() 的时候,才进行Bean的实例化进程,将Bean进行实例化。

 

我们现在,已经将mapper接口扫描出来了,并由Spring给我们封装完毕,将 BeanDefinition 注册进了 beanDefinitionMap。

这个时候,只要你启动Spring上下文。

就会报错:  BeanInstantiationException: Failed to instantiate [xxx.xxx.Xxx]: Specified class is an interface

 

因为你扫描出的只是一个普通的接口哇 !

就算封装成了BeanDefinition,也改变不了这个BeanDefinition描述的是个接口的事实啊!

Spring表示,很疑惑啊!他无法实例化啊!

 

而且,我们的终极目的是,偷天换日,将BeanDefinition描述的接口,改为我们自己定义的动态代理对象

 

如何才能将 BeanDefinition 描述的接口,修改为代理对象?

 

要知道,我不可能为每个接口都写个单独的代理,鬼知道用户提供了多少个接口?

我得有个统一的封装来表示用户提供的所有接口,并且可以在 运行时生成动态代理

此对象,还得有个 Class ,因为我得使用 BeanDefinition 的 setBeanClass() 方法,才能将该 BeanDefinition 的描述修改成我自定义的接口。

 

这个时候,就轮到 FactoryBean 出场了

这是 FactoryBean 接口,Spring 对实现了此接口的Bean进行了特殊处理。

主要表现为:

1、FactoryBean本身是个Bean
2、试图获取FactoryBean时,得到的是 FactoryBean 的 getObject() 返回的对象
3、可以通过 “&”+”name” 得到FactoryBean 本体对象

 

了解 FactoryBean 的应该不少。毕竟一些面试官总喜欢问 BeanFactory 和 FactoryBean 有什么区别这种只要背就能背会的问题。

但是,真的有人知道怎么用,在什么场景用这玩意吗?

 

好,现在场景有了,那么怎么用呢?

 

这里就涉及到 FactoryBean 配合 BeanDefinition 的 究极奥义 了。

 

已得知条件:

1、BeanDefinition 表示对一个Bean的描述,可以在实例化之前自由的更改他。

2、FactoryBean 就是一个Bean,但是 Spring 对其做了特殊处理,试图获取时,获取的是 getObject() 返回的对象

从以上条件进行推断:是不是可以在 FactoryBean 里边返回我自定义的代理对象?

 

可是,所有的mapper都要用某一个 FactoryBean 来返回代理对象。这个 FactoryBean 要怎么设计?

我是不是可以这么设计?

 

我可以用一个构造方法来接收指定的 Class 对象,然后动态返回此 Class对象的代理对象。

比如说我可以在此代理里边实现JDBC的逻辑、或者什么远程服务调用的逻辑。

 

可是,就算是这么用了:

那尼玛我 getBean()的时候 Spring 给我试图实例化出来,他也实例化不了啊?

更别提什么调用 getObject() 方法了。

 

而且这个class对象,必须给我从构造方法传进去。因为就算Spring给你实例化完了FactoryBean,他在 getObject() 时,由于没有class对象,也会报错。

这其中可没什么钩子给你 setClass 。

 

那怎么搞?

看上边已知条件第一条:  BeanDefinition 表示对一个Bean的描述,可以在实例化之前自由的更改他

 

 

我在setBeanClass() 之前,可以使用

来得到这个Bean原来的名字。 比如我那个 OneTestDao 接口,通过此方法就可以得到 :com.skypyb.dao.OneTestDao

 

然后通过

从而设置此 BeanDefinition 描述的 Bean 的构造参数

 

最终将刚刚获得的类名传入之后,Spring 解析BeanDefinition时 ,就会知道这个BeanDefinition 描述的 Bean 有个构造方法,要传的值是: com.skypyb.dao.OneTestDao

从而 成功实例化我们自定义的 FactoryBean。

 

最后的结果,就是通过我们自定义 FactoryBean 的实例对象的 getObject() 方法返回了自定义的代理,翻到文章最顶上演示,如你所见。

成功实现了 偷天换日,将BeanDefinition描述的接口,改为我们自己定义的动态代理对象

 

 

而且我的整个流程,和 MyBatis 可以说是 一模一样

不信?  MyBatis 源码 : MapperScannerRegistrar.class  请

 

 

 

整个工程的完整代码地址:  github

阅读全文
EA PLAYER &

历史记录 [ 注意:部分数据仅限于当前浏览器 ]清空

      00:00/00:00