首页 > 经验记录 > 框架

既然是一个网关。那么全局过滤器肯定是少不了的一个存在。像是鉴权、认证啥的不可能每个服务都做一次,一般都是在网关处就搞定了。

Zuul他就有很强大的过滤器体系来给人使用。

Gateway当然也不会差这么点东西。

对于SpringCloud体系来说,一切的实现都是那么的简单。那么废话不多说,直接开始写起来。

 

Gateway内部有一个接口 名为GlobalFilter,这个就是Gateway的全局过滤器接口,只要在应用中实现此接口后注册为Spring的Bean,背后就会帮你将这个实现注册到全局过滤器链条里边去。

我这里就简单的写了个模拟鉴权的过滤器实现:

虽说是鉴权,但实际上我这就是个简单的demo而已。想知道真正的Spring Security鉴权/认证怎么写?

我以前写的这个: http://skypyb.com/2019/09/jishu/kj/1050/ 应该可以帮助你。

 

看我写的这个过滤器内部实现哈,其实就是拿出Request Header中的 Authorization字段(token) 然后判断是否存在。不存在就返回错误,存在就交给链条中的下一个过滤器。

 

过滤器其实也没啥好说的,那么说说限流。

关于限流这个东西,常见的算法就是漏桶和令牌桶了,对于一个应用单机限流来说也复杂不到哪儿去。

靠着google guava包里的RateLimiter工具都能搞定大多数场景了。

不过既然人家Gateway好心好意给你搞了个限流的实现。那么还是尊重他用一下。

由于Gateway是用的Redis和lua脚本实现了令牌桶的算法,那么先导入几个需要的依赖:

 

既然是Redis,那还是先配一下Redis序列化先:

 

万事俱备,开始进行限流的具体实现了。

 

既然是限流,那么也得有个限流策略

是根据用户来限流呢?还是说根据请求路径限流?或者是IP限流?

不过这个都是由需求来决定了,我这就简单的写个根据IP来限流的。

人家也给你封装完毕了,只需要你自己实现KeyResolver这个接口就可以。

由于实现这个一般来说也就一行代码,所以我就不写个单独的类去实现了,而是直接写在配置类里边。

看,我其实只需要返回我需要限制的东西就可以了。我这里提取到用户的IP将其返回,即可实现通过ip来进行限流的策略。

不过限流相关的配置写了,那也得用起来。

这个怎么用起来? 其实直接在配置文件里配置就OK了

 

这里可以看到,除了Redis配置 ( spring.redis.** )以外。

主要就是对于Getway的配置。

在Gateway路由配置中,设置了一个filters参数。

这个是为了指定路由的各种过滤器的。这个参数也有很多种,可以参考官方讲解: https://cloud.spring.io/spring-cloud-gateway/2.0.x/single/spring-cloud-gateway.html#gateway-route-filters

我这就是指定了一个RequestRateLimiter,请求限流。

redis-rate-limiter.burstCapacity: 20     这个参数表示突发容量,即每秒可以最大通过多少次请求

redis-rate-limiter.replenishRate: 5       这个是令牌桶的补充速度,每秒往桶里边放几个令牌

key-resolver: ‘#{@ipKeyResolver}’             这个就是用上KeyResolver的具体实现了,这里用spel表达式指定我写的那个ip限制类

 

 

准备好之后将应用启动就完事了,想测的话可以用jmeter测测看,或者将请求限制写的更小一点,在网页上狂按f5也行。

 

阅读全文

 

在微服务架构中,网关是必不可少的重要组件。

这关系到了客户端“如何访问”每个服务。

以前主流的方式就是使用Netfilx的Zuul组件。但是,因为某些奇妙的原因,Netfilx全家桶都停止维护了。

我之前也写过Zuul的配置和使用方法:

微服务路由解决方案: “Zuul” 服务搭建;以及自定义Zuul过滤器的代码编写。

Spring Cloud Netflix Zuul 使用自带的 Hystrix 实现回退机制 , 并实现请求在Zuul内部的聚合功能

 

那么,既然Zuul这套东西不维护了,迟早也就是抛弃的份。这时候就到Gateway上场了。

说起Getway的工作流程,大概是这样子的: 客户端向 Spring Cloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后最终返回结果。

这么一说…是不是感觉这套流程和 SpringMVC 有点像。这么一看一下子Gateway的流程就感觉铭然于心了有木有。

 

进入正题,开始上手 Spring Cloud Getway 的配置和使用。

当然,路由网关,也是一个项目。

难么必不可少的 项目依赖管理配置文件、应用配置文件、启动类 是肯定需要的。

而他,真的就只需要这几个东西就能完成基本的路由、负载均衡功能。

 

既然要建立项目,pom.xml就是第一个。

Spring Cloud Getway 有一个特别的地方。

那就是他不使用 Web 作为服务器,而是使用 WebFlux 作为服务器。

基于此,那么 spring-boot-starter-web 这依赖就不能上了。

pom.xml完成体如下:

这pom.xml真是又丑又长。我怎么就用了maven没用gradle呢? 不禁陷入了深深的思考

既然是Alibaba体系,那么Sentinel、Nacos等依赖也是需要的。 和之前写过的几个Spring Cloud Alibaba组件的使用结合到一起。

在此之上去掉web依赖,引入 org.springframework.cloud:spring-cloud-starter-gateway 依赖就可以了。

由于过滤器等功能依然需要 Servlet 支持,故这里在依赖一个 javax.servlet:javax.servlet-api

 

 

pom.xml搞定,接下来就是最最最最重要的 application.yml:

 

其他的不说,最主要的是spring.cloud.gateway.route下面的配置。

在这里有几个很重要的点,要重点说一下。

id:  这个是路由ID,值可以随便写,但是一定要保证在整个微服务中此值是唯一的

uri:   lb://是固定写法,表示开启负载均衡。后边跟着的是你注册进nacos的其他服务名字

predicates: 这个是谓词。谓词这个词语一说出来很多人都会蒙圈。但是如果是熟悉JDK1.8的人肯定对这个有印象,Stream API里filter()方法入参就是一个Predicate,返回一个boolean类型的值。虽说Gateway里的和Strean API里的肯定不一样。但是可以这么理解,他是为了判断请求是否满足一个条件而存在的。而我设置的 – Method=GET,POST 表示匹配 GET 和 POST 请求,这俩种类型的请求就可以路由。

 

predicate当然不止Method这一个玩意,他总共有这么些设置:

After | After=2017-01-20T17:42:47.789-07:00[America/Denver]
Before | Before=2017-01-20T17:42:47.789-07:00[America/Denver]
Between | 2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]
Cookie | Cookie=chocolate, ch.p
Header | Header=X-Request-Id, \d+
Host | Host=**.somehost.org
Method | Method=GET
Path | Path=/foo/{segment}
Query | Query=baz
RemoteAddr | RemoteAddr=192.168.1.1/24

参考: https://cloud.spring.io/spring-cloud-gateway/2.0.x/single/spring-cloud-gateway.html#gateway-request-predicates-factories

 

 

那么现在

nacos也注册进去了

setinel熔断也开了

路由设置也配好了

还要什么自行车,一个启动类,给他 Run 起来,就可以搞起

 

注意,访问路径是: http://路由网关IP:路由网关Port/服务名/**  (和Zuul一样的)

随便调用个接口,能够成功获取响应就是成功啦!多开几个同样的服务跑起来就会知道,负载均衡也是实现了的。

至此 Spring Cloud Gateway 结合Spring Cloud Alibaba全家桶的路由功能、负载均衡功能已经配置完毕。

阅读全文

说起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 控制台找不到自己的服务是很正常的,只要启动时没有报错就不会有问题。

 

 

阅读全文

地址:

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 才将基本的认证、授权流程写完

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

 

 

阅读全文

 

 

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

 

本文章又名: 兼容 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