当前位置: 首页 > news >正文

怎么做情侣网站咸阳住房和城乡建设局网站

怎么做情侣网站,咸阳住房和城乡建设局网站,网页设计小白做网站,温州网站建设选择乐云seo这里写目录标题 前言温馨提示手把手带你解析 MapperScan 源码手把手带你解析 MapperScan 源码细节剖析工厂模式Jdk 代理手撕脚手架#xff0c;复刻 BeanDefinitionRegistryPostProcessor手撕 FactoryBean代理 Mapper 在 Spring 源码中的生成流程手撕 MapperProxyFactory手撕增… 这里写目录标题 前言温馨提示手把手带你解析 MapperScan 源码手把手带你解析 MapperScan 源码细节剖析工厂模式Jdk 代理手撕脚手架复刻 BeanDefinitionRegistryPostProcessor手撕 FactoryBean代理 Mapper 在 Spring 源码中的生成流程手撕 MapperProxyFactory手撕增强逻辑 InvocationHandler源码级别解读 Mapper 要被设计成接口的原因自定义 Executor 实现框架 Dao 层手写基础架构效果演示总结 前言 最近在码云搜 Es 的开源项目学学技术无意间搜到 Easy-Es 这么一个项目里面的用法和 Mybatis-Plus 一模一样当时心想我擦这个人是直接悟透了 Mybatis-Plus 吗虽然老早前看过源码。之前大概看了一下就是对 Mapper 对象进行代理植入了一些自定义逻辑而已没仔细看过实现细节现在网上居然有人直接又造了一个轮子直呼 666于是乎深入看了 Mybatis-Plus 是如何生成 Mapper 代理对象的全部源码并且一比一复刻出来了。 温馨提示 阅读以下文章了解前置知识对理解本文更有帮助 深入jdk动态代理源码解析模拟jdk动态代理完整版Factorybean与BeanFactory的区别手把手debug自动装配源码、顺带弄懂了Import等相关的源码全文3w字、超详细spring 源码解析配图文讲解顺带搞懂了循环依赖、aop底层实现 手把手带你解析 MapperScan 源码 废话不多说直接步入正题我们在使用 Mybatis 的时候要要设置 MapperScan 扫描对应的 Mapper 接口一步步点进去 发现其实就是注册了 MapperScannerConfigurer 这个 Bean 都是些常用套路。然后发现 MapperScannerConfigurer 实现了 BeanDefinitionRegistryPostProcessor、InitializingBean。 BeanDefinitionRegistryPostProcessorSpring 为我们提供的扩展点让程序员可以自己干预 Bean 的生成 InitializingBean在 Bean 填充属性populateBean完成后会调用 直接看重写了 BeanDefinitionRegistryPostProcessor 的 postProcessBeanDefinitionRegistry 方法就行看下图可以看到就是利用 ClassPathMapperScanner 路径扫描器去扫描指定包下面的类然后生成对应的 BeanDefinition 注册到 BeanDefinitionMap 中然后 Spring 会将 BeanDefinitionMap 中的所有 BeanDefinition 生成 Bean 放到 Spring 单例池里面提供给程序员使用。 然后来到 scan 源码cmdartb 查看 doScan 实现类点进第二个第一个是 Spring 实现的而我们看的是 Mybatis 的源码这里大家要注意一下 然后你会发现扫描完 basePackages 下的类生成对应的 BeanDefinition 后还会去处理一下这些 BeanDefinitionclick 进去。 发现得到的所有 Mapper 的 BeanDefinition 的 BeanClass 都被替换成了mapperFactoryBeanClass 工厂 bean 到这里我大概就明白了所有的 Mapper BeanDefinition 统一设置为 MapperFactoryBean 类型最终生成的 Bean 本质 Class 是 MapperFactoryBean 但是名字依然是原来的名字然后通过代理工厂统一生成代理对象这也是很多开源框架的常用套路。接下来验证一下我的猜想。看一下 MapperFactoryBean 构造实现了 FactoryBean 。 当我们的项目中使用了如下代码时拿到的 Bean 其实是在紧挨上图一中的 getObject 方法中创建的。 Autowired UserMapper userMapper;然后进入 getMapper 方法里面。看到确实是通过 MapperProxyFactory 代理工厂生成的代理对象 Mapper。 看到这你是不是觉得源码也不过如此对于整个简单的流程虽然走完了但是作为一个要进行开发整个轮子的开发者来说还远远不够。还需要了解更多细节 如何将指定包路径下的所有类生成 BeanDefinition 。MapperProxyFactory 如何初始化并且 MapperProxyFactory 如何根据感知生产什么类型的代理对象等 手把手带你解析 MapperScan 源码细节剖析 这部分的文章读者可选择自行跳过 knownMappers 中的数据什么时候初始化的 回到 MapperFactoryBean 类中可以看到 checkDaoConfig 方法左侧有一个这个小图标说明就是抽象接口的实现类一般为了简化操作很多框架包括我也喜欢利用抽象接口封装逻辑 点击来到了上层的实现类发现还被包裹了一层逻辑接着点向上的那个图标 来到最顶层的 checkDaoConfig 发现原来 MapperFactoryBean 居然实现了 InitializingBean 接口当 MapperFactoryBean 属性填充完成以后进行调用 afterPropertiesSet 方法触发我们的 checkDaoConfig 方法调用。 最终会发现在进行 addMapper 的时候会以 keymapperInterface valueMapperProxyFactory 的键值对放到 knownMappers 里面而 mapperInterface 其实就UserMapper 的 Class。 Mapper 是使用什么代理创建的 答看一下 MapperProxyFactory 源码得知是用的 Jdk 代理直接代理接口 如何动态的批量创建、修改 Bean 答通过实现 Spring 提供的扩展接口 BeanDefinitionRegistryPostProcessor 动态注册、修改 BeanDefinition 即可。 如何实现动态的将一个普通 Bean 改成工厂 Bean 答通过设置 BeanDefinition 的 BeanClass、ConstructorArgumentValues 替换成工厂 Bean 的 Class 即可。关键代码如下 genericBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(((GenericBeanDefinition) beanDefinition).getBeanClass()); genericBeanDefinition.setBeanClass(mapperFactoryBeanClass);源码中的 mapperInterface 是什么东西 答Mapper 对象的 Class。举个例子当项目中用到了 Autowired UserMapper userMapper;此时的 mapperInterface 就是 UserMapper.Class 为什么 Mapper 的代理对象能转换成目标对象 了解 Jdk 动态代理的都知道代理对象不能转换成目标对象只能装换成目标对象的接口实现类或者 Proxy 对象原因就是如下可以看到代理对象和目标对象半毛钱关系都没有。 代理对象 extends proxy implments 目标对象实现接口那为什么 UserMapper 的代理对象但是还能用 UserMapper 接收呢项目中应该这样使用才对啊 Autowired Proxy userMapper;工厂模式Jdk 代理手撕脚手架复刻 BeanDefinitionRegistryPostProcessor 里面的逻辑主要就是扫描指定包下面的类生成对应的 BeanDefinition然后自定义一个我们自己的后置处理器将所有 BeanDefinition 替换成工厂 Bean。读者可自行封装对应的后置处理器方便其他使用者进行扩展。整个流程对标 ClassPathMapperScanner 源码中的 doScan 逻辑。 /*** 扫描哪些包是 mapper并统一设置类型为 BaseFactoryBean*/ Slf4j Component public class RegistryPostProcessorConfig implements BeanDefinitionRegistryPostProcessor {private Class? extends BaseFactoryBean mapperFactoryBeanClass BaseFactoryBean.class;Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {//扫描指定路径下的 BeanDefinitionSetBeanDefinitionHolder beanDefinitions scan();if (beanDefinitions.size() 1) {//后置处理器全部替换成工厂 BeanfactoryBeanDefinitionPostProcess(beanDefinitions);}//注册 BeanDefinitionregister(beanDefinitions,registry);log.info(自定义 Mapper 扫描注册完成);}/*** 扫描指定包下面的类包装成一个个的 BeanDefinitionHolder我这里就简单写写直接指定了*/public SetBeanDefinitionHolder scan() {HashSetBeanDefinitionHolder beanDefinitions new HashSet();GenericBeanDefinition scanBeanDefinition new GenericBeanDefinition();scanBeanDefinition.setBeanClassName(userMapper);scanBeanDefinition.setBeanClass(UserMapper.class);GenericBeanDefinition scanBeanDefinition2 new GenericBeanDefinition();scanBeanDefinition2.setBeanClassName(studentMapper);scanBeanDefinition2.setBeanClass(StudentMapper.class);beanDefinitions.add(new BeanDefinitionHolder(userMapper,scanBeanDefinition));beanDefinitions.add(new BeanDefinitionHolder(studentMapper,scanBeanDefinition2));return beanDefinitions;}public void factoryBeanDefinitionPostProcess(SetBeanDefinitionHolder beanDefinitions) {for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitions) {GenericBeanDefinition genericBeanDefinition (GenericBeanDefinition) beanDefinitionHolder.getBeanDefinition();genericBeanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);genericBeanDefinition.setLazyInit(false);/*** 设置 bean 创建的构造 class必须设置不然 bean 无法被创建*/genericBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(((GenericBeanDefinition) beanDefinitionHolder.getBeanDefinition()).getBeanClass());genericBeanDefinition.setBeanClass(mapperFactoryBeanClass);}}public void register(SetBeanDefinitionHolder beanDefinitions, BeanDefinitionRegistry registry) {for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitions) {/*** BeanDefinition 重置了 BeanClass 为 BaseFactoryBean 后对应的 BeanClassName 会自动变成 com.zzh.service2.structure.factory.bean.BaseFactoryBean* 造成所有的 Mapper 接口的 BeanDefinition 的 BeanClassName 都是 com.zzh.service2.structure.factory.bean.BaseFactoryBean 导致注册报错* 因此自定义包装 BeanDefinitionHolder 对象设置原始 BeanName* 例如BeanDefinitionHolder(key-userMapper,value-BeanDefinition)* BeanDefinitionHolder(key-studentMapper,value-BeanDefinition)*/registry.registerBeanDefinition(beanDefinitionHolder.getBeanName(), beanDefinitionHolder.getBeanDefinition());}}Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {}}手撕 FactoryBean 实现 FactoryBean 接口同时设置成泛型让任何类型的 Mapper 接口都是转换成此 FactoryBean当 Spring 进行属性填充完成之后进行初始化 Bean 的时候会调用 InitializingBean 接口里面的方法此时我们将 UserMapper.Class 放到一个临时容器中等 BaseFactoryBean.getObject 方法被调用的时候再去容器里面拿到 UserMapper.Class 进行 Jdk 代理创建代理对象。 Data public class BaseFactoryBeanT implements FactoryBeanT, InitializingBean {/*** 如何实现动态的将一个普通 Bean 改成工厂 Bean * 通过设置 BeanDefinition 的 BeanClass、ConstructorArgumentValues 替换成工厂 Bean 的 Class 即可。关键代码如下* javascript* genericBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(((GenericBeanDefinition) beanDefinition).getBeanClass());* genericBeanDefinition.setBeanClass(mapperFactoryBeanClass);* */public BaseFactoryBean(ClassT mapperInterface) {this.mapperInterface mapperInterface;}private ClassT mapperInterface;/*** 通过 MapperProxyFactory 工厂统一生产代理对象*/Overridepublic T getObject() throws Exception {return (T) BaseMapperRegistry.getMapper(mapperInterface).newInstance(DaoTemplateFactory.getInstance().getDaoTemplate());}Overridepublic Class? getObjectType() {return mapperInterface;}Overridepublic void afterPropertiesSet() {BaseMapperRegistry.addMapper(this.mapperInterface);}Overridepublic boolean isSingleton() {return true;} }补充一嘴 Spring 中的源码逻辑BeanDefinitionMap 中所有的 BeanDefinition 都会走 CreateBean 的流程先是调用 createBeanInstance 方法创建一个实例对象然后调用 populateBean 方法为实例对象填充属性接着才是调用 InitializingBean 里面的方法。可以看到此时的 mapperInterface 是 UserMapper.Class 代理 Mapper 在 Spring 源码中的生成流程 当创建好 UserMapper 这个 Bean 的时候会调用 getObjectForBeanInstance 方法获取其实例发现 UserMapper 是个工厂 Bean于是乎调用 getObject 方法走我们的 Jdk 创建代理对象的逻辑最终放到 Ioc 容器里面的是我们自己创建的代理对象 然后顺着栈帧来到 getObject,到此整个流程结束 手撕 MapperProxyFactory 根据目标对象的 Class 生成代理对象同时 InvocationHandler 里面织入我们手写的 DaoTemplate用来与数据库进行交互。 亮点代码只有一行由于目标对象自身是一个 Mapper 接口参数二实现类的接口用的是自己本身 new Class[]{mapperInterface} 这样生成的代理对象就可以转换成目标对象了。 T proxyInstance (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, baseMapperProxy);Slf4j public class MapperProxyFactoryT {/*** 被代理对象 Class*/Getterprivate final ClassT mapperInterface;private ConcurrentHashMap methodCaches new ConcurrentHashMapObject, Object();public MapperProxyFactory(ClassT mapperInterface) {this.mapperInterface mapperInterface;}public T newInstance(MapperProxyInvokeT baseMapperProxy) {T proxyInstance (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, baseMapperProxy);log.info(proxyInstance instanceof BaseMapper:{}, proxyInstance instanceof BaseMapper);log.info(proxyInstance instanceof BaseMapper:{}, proxyInstance instanceof UserMapper);return proxyInstance;}/*** Mybatis-Plus 封装了 SqlSession 对象操作 db我这里也简单封装一个 DaoTemplate 做做样子* param daoTemplate* return*/public T newInstance(DaoTemplate daoTemplate) {MapperProxyInvokeT baseMapperProxy new MapperProxyInvokeT(daoTemplate, mapperInterface, methodCaches);return newInstance(baseMapperProxy);}/*** 为啥jdk生成的代理对象居然不支持类型转换为目标对象* https://blog.csdn.net/qq_42875345/article/details/115413716*/public static void main(String[] args) {test1();test2();}/*** 强制代理对象实现 UserMapper 接口从而实现 jdk生成的代理对象支持转换为目标对象* 关键代码new Class[]{UserMapper.class}* 这也是为什么 Mapper 要设计成接口的原因* 代理对象结构代理对象 extends proxy implments UserMapper*/static void test1() {Mapper proxyInstance (Mapper) Proxy.newProxyInstance(UserMapper.class.getClassLoader(), new Class[]{UserMapper.class}, new InvocationHandler() {Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.err.println(代理前置输出);return null;}});System.err.println(proxyInstance instanceof UserMapper); //trueSystem.err.println(proxyInstance instanceof BaseMapper);System.err.println(proxyInstance instanceof Mapper);}/*** 普通 Jdk 代理对象只实现目标对象的实现接口* 代理对象结构代理对象 extends proxy implments 目标对象实现接口*/static void test2() {Mapper proxyInstance (Mapper) Proxy.newProxyInstance(UserMapper.class.getClassLoader(), UserMapper.class.getInterfaces(), new InvocationHandler() {Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.err.println(代理前置输出);return null;}});System.err.println(proxyInstance instanceof UserMapper); //falseSystem.err.println(proxyInstance instanceof BaseMapper);System.err.println(proxyInstance instanceof Mapper);}}手撕增强逻辑 InvocationHandler 实现了 InvocationHandler 接口每当代理 Mapper 中的方法被调用的时候都会执行 invoke 中的逻辑。里面分默认方法被 default 修饰的方法与 db 查询的方法 /*** 代理 Mapper 增强逻辑*/ Slf4j public class MapperProxyInvokeT implements InvocationHandler, Serializable {private static final long serialVersionUID -6424540398559729838L;private final DaoTemplate daoTemplate;private final ClassT mapperInterface;private final MapMethod, MapperMethod methodCache;public MapperProxyInvoke(DaoTemplate daoTemplate, ClassT mapperInterface, MapMethod, MapperMethod methodCache) {this.daoTemplate daoTemplate;this.mapperInterface mapperInterface;this.methodCache methodCache;}/**** param proxy 生成的代理对象* param method 被调用的目标对象方法* param args 被调用的目标对象方法中的参数* return* throws Throwable*/Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {log.info(代理对象前置输出);try {if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, args);} else if (method.isDefault()) {/*** Mapper 自带的默认方法走这调用userMapper.say()*/return invokeDefaultMethod(proxy, method, args);}} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}/*** userMapper.seleteById(1)走这调用* 此处需要 Method 与 daoTemplate 中的方法名称、参数做匹配然后调用 daoTemplate 中的方法* 源码中也是这么干的我懒这里直接硬编码匹配 seleteById 了*/return daoTemplate.seleteById(1); // mybatis 源码中还做了方法缓存加快处理速度 // final MapperMethod mapperMethod cachedMapperMethod(method); // return mapperMethod.execute(sqlSession, args);}private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)throws Throwable {final ConstructorMethodHandles.Lookup constructor MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);if (!constructor.isAccessible()) {constructor.setAccessible(true);}final Class? declaringClass method.getDeclaringClass();return constructor.newInstance(declaringClass,MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED| MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC).unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);} }源码级别解读 Mapper 要被设计成接口的原因 这里也贴一下 UserMapper 的代码吧也解释一下 Mapper 为什么要采用接口的形式 /*** UserMapper 只能是接口如果 UserMapper 为类生成的代理对象不能转换为 UserMapper* 只能转换为 proxy、或者 BaseMapper 原因代理对象 extends proxy implments BaseMapper* 但是我们需要 Autowire UserMapper 这样使用。需要代理对象为 UserMapper 类型因此 UserMapper 只能是接口* 让生成的代理对象 extends proxy implments UserMapper*/ public interface UserMapper extends BaseMapperUser {default String say() {return UserMapper say;} }自定义 Executor 实现框架 Dao 层 代理对象会根据被调用的方法匹配 DaoTemplate 中的方法进行执行在这里面可以自行封装类似于 Mybatis 二级缓存多级 Executor 动态数据源切换的逻辑工程量巨大我这里只提供思路简单查个库给大家演示一下设计原理。到此所有组件全部开发完成。 /*** 封装原始的 jdbc 逻辑可扩展组件多级缓存查询、多级 Executor 查询、数据库连接池切换等等*/ public class DaoTemplate {String driver com.mysql.cj.jdbc.Driver;String url url;String username root;String password pwd;public Connection getConnection() throws SQLException, ClassNotFoundException {Class.forName(driver);return DriverManager.getConnection(url, username, password);}//随便写写了直接拼接public User seleteById(Integer id) throws SQLException, ClassNotFoundException {String sql select * from user where id id;ResultSet resultSet getConnection().createStatement().executeQuery(sql);resultSet.next();return new User().setId(resultSet.getInt(id)).setName(resultSet.getString(name));}}手写基础架构效果演示 基础依赖包如下 可以看到使用我们手撕的 UserMapper 可以成功的查到 db 中的数据 总结 本文基于源码分析了 Mybatis 中代理 Mapper 创建的详细流程基于理解一比一手撕复刻了出来期间遇到的问题都总结在注释里面了。有人说会这个有啥用会这个你可以将所有和数据库打交道的技术都封装成类似于 Mybatis-Plus 的框架造福全宇宙让技术不在复杂让小学生都会写代码你就是明日之星
http://www.dnsts.com.cn/news/39895.html

相关文章:

  • 北京做网站的开发公司网站的引导页面是什么意思
  • .湖南省住房和城乡建设厅网站wordpress没有加载图片
  • 在线网站建设课程2023年税收优惠政策
  • windows系统做网站网站入股云建站
  • 字形分析网站小程序制作永久免费
  • 免费室内设计软件有哪些seo如何快速排名百度首页
  • 图像处理与网站开发做网站放到百度上需要什么
  • 我有域名怎么建网站江西南昌网站制作
  • 青岛网站制作辰星辰郑州网站建设模板
  • asp网站没有数据库哈尔滨网站建设服务公司
  • 第三方编辑网站怎么做dw如何做网站界面
  • 烟台制作网站软件山西网站制作平台
  • 国外做机械设计任务的网站最好的关键词排名优化软件
  • 织梦大气企业网站模板(扁平化风格)深圳国税局网站怎么做票种核定
  • 网站建设相关ppt创建属于自己的网站
  • 桑福生物科技网站开发内部网站建设要求
  • 男鞋 东莞网站建设广州建设
  • 网站设置的关键词自己的公众号怎么做的
  • 有那种做订单的网站吗黄页内容
  • dns解析失败登录不了网站成立公司注册资金可以随便写吗
  • 中小型网站建设精英自定义wordpress导航图标
  • 网站程序源码上传到空间打开网站首页还是显示的程序原源代码网站建设设计要点
  • 企业网站页面图片个人发布房源的网站
  • flashfxp上传多个网站深圳公司核名工商官网
  • 个人网站建设的过程百度竞价做网站
  • 网站设计息做网站的价
  • 建设厅投诉网站首页浙江省建设厅网站首页
  • 建设银行海淀支行 网站一个网站开发语言
  • 合肥网站建设 毅耘快照首页排名优化服务
  • 北京大学学术学风建设网站wordpress 只显示摘要