Sprout Blog - 软件开发
type: channel
link: https://blog.yeskery.com/category/soft_dev/
description: 软件开发
items: 注意BeanPostProcessor启动时对依赖Bean的“误伤”陷阱（is not eligible for getting processed by all...）
items: Nacos服务注册中心原理总结
items: Spring事务@Transactional的创建和执行过程
items: Spring AOP 动态代理的执行过程
items: Spring AOP动态代理的创建过程
items: Spring Bean生命周期
items: ApplicationContext的初始化过程
items: Spring使用TransactionalEventListener解决事务未提交读取不到数据问题
items: 「Elasticsearch」ES重建索引怎么才能做到数据无缝迁移呢？
items: Java的动态绑定和静态绑定

注意BeanPostProcessor启动时对依赖Bean的“误伤”陷阱（is not eligible for getting processed by all...）
type: item
date: 2022-12-19T23:04:59+08:00
description: 前言什么是BeanPostProcessor场景模拟关于BeanPostProcessor的加载顺序@Async异步注解失效得原因关于BeanPostProcessorChecker注意避免BeanPostProcessor启动时对依赖的Bean造成误伤前言本篇博文和Spring的上下文启动有较强的关联性，同时需要读者对Spring中的BeanPostProcessor有较为熟悉的了解。若之前没有接触过的同学，建议先点击一下相关阅读的文章列表，先对Spring容器有个大致的了解会效果更佳~这是曾发生在我原公司工作中的一个Spring项目的真实场景案例：简单的描述就是在使用Spring整合@Async、security的时候，出现一个诡异的现象：我把security整合进后原来的@Async就木有生效了，但是如果不把security集成进来的话，就能正常work当时还以为是spring-security的问题，甚至以为是它的bug，现在想起来确实是自己当初图样图森破，切忌不要轻易下结论啊~其实当初我也没找到根本原因，而是通过另外一种集成方式绕过了就继续撸码了。但是我心里一直记着此事，因为我认为一个问题你不知道它根本原因的时候，它就像个定时炸弹，随时可能被引爆。介于此机会，所以此处拿出来跟大家分享分享，避免采坑哈~记忆中唯一线索：BeanPostProcessorChecker这个后置处理器输出了一句 xxx is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)这样的日志~~~~今天突然想起，其实这就是个很大的突破口（因为这句日志一般情况下是不会输出的~）本文就不还原当时的场景了，而是以一个模拟的场景进行讲解、定位问题最后解决问题什么是BeanPostProcessorBeanPostProcessor是Spring的Bean工厂中一个非常重要的钩子，允许Spring框架在新创建Bean实例时对其进行定制化修改。比如我们对Bean内容进行修改、创建代理对象等等~BeanPostProcessor本身也是一个Bean，一般而言其实例化时机要早过普通的Bean，但是BeanPostProcessor有时也会依赖一些Bean，这就导致了一些普通Bean的实例化早于BeanPostProcessor的可能情况，由此如果使用不当，就会造成一些问题场景模拟现在通过我自己构造的一个场景，来模拟当时出现的问题~先看看只使用@Aysnc的现象：@EnableAsync @Configuration public class AsyncConfig implements AsyncConfigurer { @Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // 这一步千万不能忘了，否则报错： java.lang.IllegalStateException: ThreadPoolTaskExecutor not initialized // 而且最好放在最上面 否则下面set方法对Executor都不会生效 executor.initialize(); executor.setCorePoolSize(10); //核心线程数 executor.setMaxPoolSize(20); //最大线程数 executor.setQueueCapacity(1000); //队列大小 executor.setKeepAliveSeconds(300); //线程最大空闲时间 executor.setThreadNamePrefix("fsx-Executor-"); 指定用于新创建的线程名称的前缀。 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 拒绝策略（一共四种，此处省略） return executor; } // 异常处理器：当然你也可以自定义的，这里我就这么简单写了~~~ @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return new SimpleAsyncUncaughtExceptionHandler(); } }使用@Async@Service public class…

Nacos服务注册中心原理总结
type: item
date: 2022-10-25T10:46:00+08:00
description: Nacos服务注册中心原理总结Nacos服务注册中心原理总结客户端启动时会将当前服务的信息包含ip、端口号、服务名、分组名、集群名等信息封装为一个Instance对象，准备向Nacos服务器注册服务，在注册服务之前，会根据Instance中的信息创建一个BeatInfo对象，然后创建一个定时任务，每隔一段时间向Nacos服务器发送PUT请求并携带相关信息，作为定时心跳连接，服务器端在接收到心跳请求后，会去检查当前服务列表中有没有该实例，如果没有的话将当前服务实例重新注册，注册完成后立即开启一个异步任务，更新客户端实例的最后心跳时间，如果当前实例是非健康状态则将其改为健康状态；​2. 心跳定时任务创建完成后，通过POST请求将当前服务实例信息注册进Nacos服务器，服务器端在接收到注册实例请求后，会将请求携带的数据封装为一个Instance对象，然后为这个服务实例创建一个服务Service，一个Service下可能有多个服务实例，服务在Nacos保存到一个ConcurrentHashMap中，格式为命名空间为key，value为map，分组名和服务名为内层map的key，value为服务数据，Map(namespace,Map(group::serviceName, Service))；​3. 服务创建完成之后，开启一个定时任务（5s），检查当前服务中的各个实例是否在线，如果实例上次心跳时间大于15s就将其状态设置为不健康，如果超出30s，则直接将该实例删除；​4. 然后将当前实例添加到对应服务列表中，这里会通过synchronized锁住当前服务，然后分两种情况向集群中添加实例，如果是持久化数据，则使用基于CP模式的简单Raft协议，通过leader节点将实例数据更新到内存和磁盘文件中，并且通过CountDownLatch实现了一个简单的raft写入数据的逻辑，必须集群半数以上节点写入成功才会给客户端返回成功；​5. 如果是非持久话实例数据，使用的是基于AP模式的Distro协议，首先向任务阻塞队列添加一个本地服务实例改变任务，去更新本地服务列表，然后在遍历集群中所有节点，分别创建数据同步任务放进阻塞队列异步进行集群数据同步，不保证集群节点数据同步完成即可返回；​6. 在将服务实例更新到服务注册表中时，为了防止并发读写冲突，采用的是写时复制的思想，将原注册表数据拷贝一份，添加完成之后再替换回真正的注册表，更新完成之后，通过发布服务变化事件，将服务变动通知给客户端，采用的是UDP通信，客户端接收到UDP消息后会返回一个ACK信号，如果一定时间内服务端没有收到ACK信号，还会尝试重发，当超出重发时间后就不在重发，虽然通过UDP通信不能保证消息的可靠抵达，但是由于Nacos客户端会开启定时任务，每隔一段时间更新客户端缓存的服务列表，通过定时轮询更新服务列表做兜底，所以不用担心数据不会更新的情况，这样既保证了实时性，又保证了数据更新的可靠性；​7. 服务发现：客户端通过定时任务定时从服务端拉取服务数据保存在本地缓存，服务端在发生心跳检测、服务列表变更或者健康状态改变时会触发推送事件，在推送事件中会基于UDP通信将服务列表推送到客户端，同时开启定时任务，每隔10s定时推送数据到客户端。

Spring事务@Transactional的创建和执行过程
type: item
date: 2022-04-09T11:23:00+08:00
description: @Transactional注解的运行原理@Transactional事务动态代理对象创建过程@Transactional事务动态代理对象执行过程事务失效的常见场景@Transactional注解的运行原理首先生成事务类的动态代理对象，在执行动态代理事务类的事务方法时，会执行事务拦截器TransactionInterceptor；接着事务核心组件TransactionAspectSupport开始执行事务方法主流程；首先调用Connection.setAutoCommit(false)方法开启事务，本质是基于jdbc连接开启事务方法；接着调用目标方法invocation.proceedWithInvocation()，依旧采用ReflectiveMethodInvocation方法调用组件调用目标方法；在目标方法执行完成后，判断目标方法是否发生异常；如果发生异常，就调用Connection.rollback()方法回滚事务，本质是基于jdbc连接回滚事务；如果没有发生异常，就调用Connection.commit()方法提交事务，本质是基于jdbc连接提交事务；@Transactional事务动态代理对象创建过程该创建过程和AOP的创建过程一致，只不过事务使用的是BeanFactoryTransactionAttributeSourceAdvisor事务增强advisor来处理事务代理对象；从IOC容器中获取bean；调用bean后置处理器AbstractAutoProxyCreator的postProcessBeforeInstantiation()方法；判断是否指定targetSource，如果指定了targetSource会跳过bean的实例化和初始化流程，也就是会直接创建AOP代理；如果没有指定targetSource，执行正常创建bean的流程，执行bean的初始化，执行initMethod方法；调用bean后置处理器AbstractAutoProxyCreator的postProcessAfterInitialization()方法；执行wrapIfNecessary方法包装需要被代理的bean；获取所有的增强advisors；获取xml方式配置的增强advisors；此处会获取到BeanFactoryTransactionAttributeSourceAdvisor事务增强advisor；获取@Aspect注解配置的增强advisors，调用BeanFactoryAspectJAdvisorsBuilder的buildAspectJAdvisors方法；获取IOC容器中的所有bean，判断是否添加@Aspect注解；通过@Aspect注解配置的增强advisors和事务增强的advisor合并在一起将所有的advisors返回；调用findAdvisorsThatCanApply方法，从所有增强的advisor中找到与当前bean相匹配的增强Advisors；遍历当前bean中所有方法依次与advisor的切点表达式进行匹配;判断目标类不是java自带的类并且不是Orderd接口类型，如果不满足该条件就不处理当前bean；如果满足上面的条件，就获取@Transactional注解中配置的属性（AbstractFallbackTransactionAttributeSource#getTransactionAttribute）；判断目标方法是否是public方法，如果不是，则不处理当前方法；判断目标方法上是否可以获取到@Transactional事务属性，如果可以获取到，就将BeanFactoryTransactionAttributeSourceAdvisor事务增强advisor添加到当前方法的advisors中；如果未从方法上获取事务属性，就尝试从类上获取@Transactional事务属性，如果可以获取到，就将BeanFactoryTransactionAttributeSourceAdvisor事务增强advisor添加到当前方法的advisors中；创建ProxyFactory对象，并设置相关属性；调用createAopProxy方法，获取构造方法默认创建的DefaultAopProxyFactory代理工厂对象，开始选择代理方式并创建代理对象；详细创建流程可以参考Spring AOP 动态代理的创建过程@Transactional事务动态代理对象执行过程调用事务代理对象的事务方法；调用AdvisedSupport#getInterceptorsAndDynamicInterceptionAdvice方法获取拦截器链chain；从methodCache缓存中查找是否存在目标方法对应的拦截器链；如果没有从…

Spring AOP 动态代理的执行过程
type: item
date: 2022-04-07T21:24:00+08:00
description: JDK代理调用代理类$Proxy的方法时，会调用super.h.invoke方法，该方法实际调用的是JdkDynamicAopProxy#invoke方法；判断当前方法是equals方法且实现类未对equals方法进行重新，如果是，就调用JdkDynamicAopProxy#equals方法，返回执行结果；判断当前方法是hashCode方法且实现类未对hashCode方法进行重新，如果是，就调用JdkDynamicAopProxy#hashCode方法，返回执行结果；cglib代理实际调用的是DynamicAdvisedInterceptor#intercept方法；调用AdvisedSupport#getInterceptorsAndDynamicInterceptionAdvice方法获取拦截器链chain；从methodCache缓存中查找是否存在目标方法对应的拦截器链；如果没有从methodCache缓存中找到对应的拦截器链，从ProxyFactory中获取目标类对应的类级别增强advisors；依次遍历所有的增强advsiors是否与目标方法匹配，该过程与AOP创建过程中的匹配类似；如果是advisor是IntroductionAdvisor引介增强的实现类，那么就进行类级别进行匹配（不常用）；默认的InstantiationModelAwarePointcutAdvisorImpl实现的是普通增强PointcutAdvisor接口，使用切点Pointcut进行方法级别的匹配；先使用切点表达式和目标类进行匹配，确保类级别满足切点表达式；遍历当前bean中的所有方法，使用aspectj提供的方法（SignaturePattern#matchesExactlyMethod），依次对切点表达式对方法进行精确匹配；判断方法参数是否匹配；判断方法名称是否匹配；判断方法抛出异常是否匹配；判断方法返回值是否匹配；调用DefaultAdvisorAdapterRegistry#getInterceptors方法获取advisor中的拦截器；调用InstantiationModelAwarePointcutAdvisorImpl#findAspectJAnnotationMethod方法扫描增强方法上的aspectJ注解；根据apectJ注解的类型创建不同的Advice;@Around -> AspectJAroundAdvice；@Before -> AspectJMethodBeforeAdvice；@After -> AspectJAfterAdvice；@AfterReturning -> AspectJAfterReturningAdvice；@AfterThrowing -> AspectJAfterThrowingAdvice；其中AspectJAfterAdvice、AspectJAroundAdvice、AspectJAfterThrowingAdvice三种Advice都实现了MethodInterceptor接口，因此这三种Advice本身就是拦截器；判断advice是否实现MethodInterceptor接口，如果advice接口MethodInterceptor接口，就将advice添加到interceptors中，此处会处理@Around、@After、@AfterThrowing三种注解；从默认配置的适配器AdvisorAdapter：MethodBeforeAdviceAdapter、AfterReturningAdviceAdapter、ThrowsAdviceAdapter中循环判断是否支持当前的Advice，如果支持，就将advice包装为拦截器并放入interceptors中，此处会处理@Before、@AfterReturning两种注解；最终将匹配的Advisor收集到interceptorList，并放到methodCache缓存中，以便下一次从缓存获取；如果拦截器链chain为空，则通过反射调用目标方法，将结果返回；如果拦截器链chain不为空，就将拦截器链chain统一封装为MethodInvocation；调用ReflectiveMethodInvocation#proceed，执行拦截器链；执行拦截器ExposeInvocationInterceptor#invoke方法，将拦截器链暴露到ThreadLocal中，同一个线程可以通过ThreadLocal获取当前的拦截器链，继续执行下一个拦截器的invoke方法；执行拦截器AspectJAfterThrowingAdvice#invoke方法，该方法继续执行下一个拦截器的invoke方法，并对invoke方法进行try...cat…

Spring AOP动态代理的创建过程
type: item
date: 2022-04-02T18:28:00+08:00
description: 从IOC容器中获取bean；调用bean后置处理器AbstractAutoProxyCreator的postProcessBeforeInstantiation()方法；判断是否指定targetSource，如果指定了targetSource会跳过bean的实例化和初始化流程，也就是会直接创建AOP代理；如果没有指定targetSource，执行正常创建bean的流程，执行bean的初始化，执行initMethod方法；调用bean后置处理器AbstractAutoProxyCreator的postProcessAfterInitialization()方法；执行wrapIfNecessary方法包装需要被代理的bean；获取所有的增强advisors；获取xml方式配置的增强advisors；获取@Aspect注解配置的增强advisors，调用BeanFactoryAspectJAdvisorsBuilder的buildAspectJAdvisors方法；获取IOC容器中的所有bean，判断是否添加@Aspect注解；添加了@Aspect注解的切面类，调用doWithMethods()方法遍历切面类的每个方法，找到其中标注了@Point、@Around、@Before、@After、@AfterReturning、@AfterThrowing六种注解的方法；找到标注了上面六种注解的方法后，构建一个AspectJExpressionPointcut切点对象，并设置注解中的切入点表达式；通过增强方法candidateAdviceMethod和切点AspectJExpressionPointcut等信息通过InstantiationModelAwarePointcutAdvisorImpl的构造方法构建出一个Advisor；如果bean是单例就将<beanName,advisors>以键值对的形式缓存起来，如果bean不是单例，就将<beanName,factory>以键值对的形式缓存起来，方便下一次直接使用factory快速构建Advisors，并继续循环处理下一个bean；调用findAdvisorsThatCanApply方法，从所有增强的advisor中找到与当前bean相匹配的增强Advisors；遍历当前bean中所有方法依次与advisor的切点表达式进行匹配;如果是advisor是IntroductionAdvisor引介增强的实现类，那么就进行类级别进行匹配（不常用）；默认的InstantiationModelAwarePointcutAdvisorImpl实现的是普通增强PointcutAdvisor接口，使用切点Pointcut进行方法级别的匹配；先使用切点表达式和目标类进行匹配，确保类级别满足切点表达式；遍历当前bean中的所有方法，使用aspectj提供的方法（SignaturePattern#matchesExactlyMethod），依次对切点表达式对方法进行精确匹配；判断方法参数是否匹配；判断方法名称是否匹配；判断方法抛出异常是否匹配；判断方法返回值是否匹配；将所有匹配的Advisors使用Order接口进行排序并返回；创建ProxyFactory对象，并设置相关属性；从AnnotationAwareAspectJAutoProxyCreator复制proxyTargetClass、optimize、exposeProxy、frozen、opaque等属性；判断是基于类代理还是接口代理，也就是cglib代理还是jdk代理；判断beanDefinition中是否定义了preserveTargetClass属性，如果配置为true就将proxyTargetClass设置为true，表示使用cglib进行类代理；判断目标类是否有如何要求的接口，如有符合要求的接口，就将接口添加到proxyFatory的interfaces属性中；如果目标类没有符合要求的接口，将proxyTargetClass设置为true，表示使用cglib进行类代理。将匹配后的增强拦截器和普通拦截器进行合并，并将所有的拦截器统一调用warp方法封装为Advisor类型；调用子类扩展方法AbstractAutoProxyCreator#customizeProxyFactory方法来定制自定义ProxyFactory；调用createAopProxy方法，获取构造方法默认创建的DefaultAopProxyFactory代理工厂对象，开始选择代理方式并创建代理对象；如果没有开启优化策略、并且没有设置基于类代理，且目标类实现了接口但不是SpringProxy类型的，就会使用jdk代理；如果满足上面的某一个条件，就会再次判断目标类是否是一个…

Spring Bean生命周期
type: item
date: 2022-03-16T16:30:00+08:00
description: Spring Bean生命周期Spring Bean生命周期调用 getBean(name) 方法来加载name对应的单例对象，这里只分析Spring的单例对象生命周期。转换并解析bean的名称，处理bean别名，获取到真正的bean名称；如果beanName是以&开头的，就返回FactoryBean的工厂对象本身；如果获取的是以FactoryBean工厂自定义创建的实例对象，那么就通过FactoryBean的getObject方法来创建单例对象；从缓存中获取单例bean；先从单例缓存（一级缓存 singletonObjects）中获取；如果一级获取不到，则判断当前bean是否正在创建，如果当前bean正在创建，则从早期单例缓存（二级缓存 earlySingletonObjects）中获取；如果二级缓存获取不到，则判断当前容器是否允许早期引用，如果允许早期引用，则从对象工厂缓存（三级缓存 singletonFactories）获取bean的对象工厂ObjectFactory；调用ObjectFactory的getObject方法创建早期单例bean，并将早期单例添加到早期单例缓存（二级缓存 earlySingletonObjects）中。如果成功获取到单例bean，先尝试通过FactoryBean来实例化bean；判断name是否以&开头，如果单例bean是FactoryBean实例，返回单例bean;判断单例是否是FactoryBean的实例，调用FactoryBean的getObject方法创建单例bean，返回单例bean。未获取到单例bean，开始创建单例bean；检查当前是否存在prototy类型的bean正在创建（prototy类型的bean无法解决循环依赖的问题）；标记beanName对应的bean开始创建；获取bean的BeanDefinition并封装为RootBeanDifition（RootBeanDifition包含父类Bean的定义信息）；注册并提前初始化bean依赖的那些bean；提供一个扩展点，通过后置处理器InstantiationAwareBeanPostProcessor的postProcessBeforeInstantiation方法创建并返回一个代理对象，结束创建流程；记录beanName对应的bean开始创建；正式创建单例bean；解析BeanDifinition中bean属性class；在BeanDifinition中预先标记没有重载的方法；通过反射创建bean的实例；将bean的实例封装到BeanWrapperImpl中；通过后置处理器MergedBeanDefinitionPostProcessor的方法postProcessMergedBeanDefinition用来合并一些信息到BeanDefinition，例如将添加了@Autoired、@Resource等注解信息加入到BeanDefinition中，默认的处理器有：CommonAnnotationBeanPostProcessor、AutowiredAnnotationBeanPostProcessor、InitDestroyAnnotationBeanPostProcessor等；当前bean为单例，设置允许循环且bean正在创建；添加创建早期单例bean的对象工厂ObjectFactory到三级缓存中（对象工厂缓存 singleFactories）；调用后置处理器InstantiationAwareBeanPostProcessor的postProcessAfterInstantiation方法来去修改bean的状态，如果该方法返回false，将会跳过步骤9及步骤10（即为bean填充各种属性值）；通过后置处理器InstantiationAwareBeanPostProcessor的postProcessProperties方法处理bean属性值的扩展点，其中AutowiredAnnotationBeanPostProcessor后置处理器用于处理带有@Autowired和@Value注解的字段，利用反射API来完成属性值的注入；为bean填充各种属性值；调用感知接口的方法（即相关Aware接口），为bean注入容器中的内置对象；在bean初始化之前通过后置处理器BeanPostProcessor的postProcessBeforeInitialization方法调整bean；通过后置处理器InitDestroyAnnotationBeanPostProcessor的postProcessBeforeInitialization方法来执行@PostConstruct注解标注的初始化方法；执行bean中的初始化方法：先…

ApplicationContext的初始化过程
type: item
date: 2022-03-16T16:28:00+08:00
description: ApplicationContext的初始化过程ClassPathXmlApplicationContextAnnotationConfigApplicationContextConfigurationClassPostProcessorpostProcessBeanDefinitionRegistry方法BeanFactoryPostProcessor方法ApplicationContext的初始化过程ClassPathXmlApplicationContextClassPathXmlApplication会接收一个applicationContext.xml（名称自定义）在类路径上描述spring bean的xml文件的构造方法。初始化资源管理器PathMatchingResourcePatternResolver；解析xml路径上的占位符${...}；调用refresh()方法，该方法是高级容器的启动主要方法。初始化容器上下文环境，主要内容为：在上下环境中添加属性值用于替换占位符${...}；检测指定的环境变量是否已经配置。初始化低级容器BeanFactory：创建容器 DefaultListableBeanFactory；定制容器，为容器设置参数：是否允许BeanDefinition被覆盖；是否允许循环依赖引用。将xml文件的解析交给XmlBeanDefinitionReader；解析并注册，XmlBeanFactory的主要执行流程：从classpath路径加载xml的Resource；检验并解析Resource资源得到Document；根据Document解析xml的各种属性标签；将解析到的信息封装到BeanDefinition中；注入到Spring容器beanDefinitionMap中。为BeanFacotry做一些初步的功能扩展：添加对SPEL表达式语言的支持；注册属性编辑器；添加执行器感知接口的后处理器；添加ApplicationContext额外需要忽略的感知接口；为指定接口注册对应的实现类BeanFactoryPostProcessor接口方法的执行；处理参数传进来的beanFactoryPostProcessor；从beanFactory获取所有的BeanDefiitionRegistryPostProcessor（PriorityOrdered接口和Ordered接口进行排序）；从beanFactory中获取所有BeanFactoryPostProcessor（PriorityOrdered接口和Ordered接口进行排序）。注册BeanPostProcessor：从beanFactory中获取所有BeanPostProcessor（PriorityOrdered接口和Ordered接口进行排序）注册到Spring容器中；初始化消息源MessageSource；初始化事件广播器ApplicationEventMulticaster；注册监听器ApplicationListener；初始化非延迟加载的单例；开启Spring的生命周期。AnnotationConfigApplicationContextAnnotationConfigApplicationContext会接收一个扫描的包路径（名称自定义）的构造方法。委托ClassPathBeanDefinitionScanner，将扫描的包路径转换为文件路径格式，例如：classpath*:com/test/**/*.class，扫描出路径下的所有类并转换为资源Resource数组；通过注解过滤器，过滤出标注了@Component的类并封装为注解类型的AnnotatedBeanDefinition接口的实现类AnnotatedGenericBeanDefinition；筛选出符合实例化bean条件的BeanDefinition，放入Set<BeanDefinition> candidates；循环处理每个BeanDefinition：解析@Scope注解的信息，并设置到BeanDefinition中；设置BeanDefinition默认的一些属性值；解析其它注解信息：@Lazy、@Primary、@DependsOn、@Role、@Description；将BeanDefinition封装成BeanDefinitionHolder，注入到Spring容器beanDefinitionMap中；调用refresh()方法，该方法是高级容器的启动主要方法。初始化容器上下文环境；初始化低级容器ConfigurableListableBeanFactory；为BeanFacotry做一些初步的功能扩展：添加对SPEL表达式语言的支持；注册属性编辑器；添加执行器感…

Spring使用TransactionalEventListener解决事务未提交读取不到数据问题
type: item
date: 2021-08-05T16:52:26+08:00
description: 背景解决问题方案方式一方式二背景在项目中，往往需要执行数据库操作后，发送消息或事件来异步调用其他组件执行相应的操作，例如：用户注册后发送激活码；配置修改后发送更新事件等。但是，数据库的操作如果还未完成，此时异步调用的方法查询数据库发现没有数据，这就会出现问题。伪代码如下：void saveUser(User u) { //保存用户信息 userDao.save(u); //触发保存用户事件 applicationContext.publishEvent(new SaveUserEvent(u.getId())); } @EventListener void onSaveUserEvent(SaveUserEvent event) { //获取事件中的信息（用户id） Integer id = event.getEventData(); //查询数据库，获取用户（此时如果用户还未插入数据库，则返回空） User u = userDao.getUserById(id); //这里可能报空指针异常！ String phone = u.getPhoneNumber(); MessageUtils.sendMessage(phone); }解决问题方案解决思路是在事务提交后再做其他的处理（如异步发消息处理等），这里还是从Spring执行事务的过程中入手，Spring事务的处理过程不再分析，这里直接看Spring事务增强器TransactionInterceptor的核心处理流程，源码如下：protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation) throws Throwable { // 获取事务属性 final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass); //加载配置中配置的TransactionManager final PlatformTransactionManager tm = determineTransactionManager(txAttr); final String joinpointIdentification = methodIdentification(method, targetClass, txAttr); // 声明式事务的处理 if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) { TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification); Object retVal = null; //...... retVal = invocation.proceedWithInvocation(); //...... commitTransactionAfterReturning(txInfo); return retVal; } else { // 编程式事务的处理...... } //...... }这里主要看声明式事务的处理，因为编程式事务的处理及提交都是用户在编码中进行控制。在声明式事务处理中，当方法执行完后，会执行 commitTransactionAfterReturning 方法来进行提交事务，该方法在 TransactionAspectSupport 类中，源码如下：protected void commitTransactionAfterReturning(TransactionInfo txInfo) { if (txInfo != null && txInfo.hasTransaction()) { txInfo.getTransactionManager().commit(txInfo.getTransactionStatus()); } }再看 commit 方法，该方法在 AbstractPlatformTransactionManager 类中，源码如下：public final void commit(TransactionStatus status) throws TransactionException { // 这里省略很多代码，如事务回滚...... processCommit(defStatus)…

「Elasticsearch」ES重建索引怎么才能做到数据无缝迁移呢？
type: item
date: 2021-04-23T15:19:52+08:00
description: 背景步骤新建索引将旧索引数据copy到新索引同步等待异步执⾏op_type 参数conflicts 配置批次大小配置根据taskId可以实时查看任务的执行状态删除旧索引使用别名通过别名访问新索引背景众所周知，Elasticsearch是⼀个实时的分布式搜索引擎，为⽤户提供搜索服务。当我们决定存储某种数据，在创建索引的时候就需要将数据结构，即Mapping确定下来，于此同时索引的设定和很多固定配置将不能改变。那如果后续业务发生变化，需要改变数据结构或者更换ES更换分词器怎么办呢？为此，Elastic团队提供了很多通过辅助⼯具来帮助开发⼈员进⾏重建索引的方案。 如果对 reindex API 不熟悉，那么在遇到重构的时候，必然事倍功半，效率低下。反之，就可以方便地进行索引重构，省时省力。步骤假设之前我们已经存在一个blog索引，因为更换分词器需要对该索引中的数据进行重建索引，以便支持业务使用新的分词规则搜索数据，并且尽可能使这个变化对外服务没有感知，大概分为以下几个步骤：​新增⼀个索引blog_lastest，Mapping数据结构与blog索引一致将blog数据同步至blog_lastest删除blog索引数据同步后给blog_lastest添加别名blog新建索引在这里推荐一个ES管理工具Kibana，主要针对数据的探索、可视化和分析。kibana_manage.jpgput /blog_lastest/ { "mappings":{ "properties":{ "title":{ "type":"text", "analyzer":"ik_max_word" }, "author":{ "type":"keyword", "fields":{ "seg":{ "type":"text", "analyzer":"ik_max_word" } } } } } }将旧索引数据copy到新索引同步等待接⼝将会在 reindex 结束后返回POST /_reindex { "source": { "index": "blog" }, "dest": { "index": "blog_lastest" } }在 kibana 中的使用如下所示kibana_manage_use.jpg当然高版本（7.1.1）中，ES都有提供对应的Java REST Client，比如ReindexRequest reindexRequest = new ReindexRequest(); reindexRequest.setSourceIndices("blog").setSource.setDestIndex("blog_lastest"); TaskSubmissionResponse taskSubmissionResponse = client.submitReindexTask(reindexRequest, RequestOptions.DEFAULT);为了防止赘述，接下来举例全部以kibana中请求介绍，如果有需要用Java REST Client，可以自行去ES官网查看。异步执⾏如果 reindex 时间过⻓，建议加上 wait_for_completion=false 的参数条件，这样 reindex 将直接返回 taskId。POST /_reindex?wait_for_completion=false { "source": { "index": "blog" }, "dest": { "index": "blog_lastest" } }返回：{ "task" : "dpBihNSMQfSlboMGlTgCBA:4728038" }op_type 参数op_type 参数控制着写入数据的冲突处理方式，如果把 op_type 设置为 create【默认值】，在 _reindex API 中，表示写入时只在 dest index中添加不存在的 doucment，如果相同的 document 已经存在，则会报 version confilct 的错误，那么索引操作就会失败。【这种方式与使用 _create API 时效果一致】POST _reindex { "source": { "index": "blog" }, "dest": { "index": "blog_lastest", "op_type": "create" } }如果这样设置了，也就不存在更新数据的场景了【冲突数据无法写入】，我们也可以把 op_type 设置为 index，表示所有的数据全部重新索引创建。conflicts 配置默认情况下，当发生 version conflict 的时候，_reindex 会被 abort，任务终止【此时数据还没有 rein…

Java的动态绑定和静态绑定
type: item
date: 2021-03-24T17:02:00+08:00
description: 引言静态绑定动态绑定合适的方法属性是动态绑定的吗常见面试问题重写和重载的区别重写（override）重载（overload）静态属性和静态方法能否被继承，能否重写引言在Java方法调用的过程中，JVM是如何知道调用的是哪个类的方法源代码呢？这就涉及到程序绑定这个概念。程序绑定指的是一个方法的调用与方法所在的类(方法主体)关联起来。对Java来说，绑定分为静态绑定和动态绑定；或者叫做前期绑定和后期绑定。静态绑定在程序执行前方法已经被绑定，此时由编译器或其它连接程序实现。例如：C。针对Java，可以简单地理解为程序编译期的绑定。这里特别说明一点，Java当中的方法只有final，static，private和构造方法是静态绑定。请看如下Java代码：//被调用的类 package hr.test; class Father{ public static void f1(){ System.out.println("Father— f1()"); } } //调用静态方法 import hr.test.Father; public class StaticCall{ public static void main(){ Father.f1(); //调用静态方法 } }上面的源代码中执行方法调用的语句Father.f1()被编译器编译成了一条指令：invokestatic #13。我们看看JVM是如何处理这条指令的：(1) 指令中的#13指的是StaticCall类的常量池中第13个常量表的索引项(关于常量池详见《Class文件内容及常量池 》)。这个常量表(CONSTANT_Methodref_info ) 记录的是方法f1信息的符号引用(包括f1所在的类名，方法名和返回类型)。JVM会首先根据这个符号引用找到方法f1所在的类的全限定名: hr.test.Father。(2) 紧接着JVM会加载、链接和初始化Father类。(3) 然后在Father类所在的方法区中找到f1()方法的直接地址，并将这个直接地址记录到StaticCall类的常量池索引为13的常量表中。这个过程叫常量池解析 ，以后再次调用Father.f1()时，将直接找到f1方法的字节码。(4) 完成了StaticCall类常量池索引项13的常量表的解析之后，JVM就可以调用f1()方法，并开始解释执行f1()方法中的指令了。通过上面的过程，我们发现经过常量池解析之后，JVM就能够确定要调用的f1()方法具体在内存的什么位置上了。实际上，这个信息在编译阶段就已经在StaticCall类的常量池中记录了下来。这种在编译阶段就能够确定调用哪个方法的方式，我们叫做静态绑定机制 。除了被static修饰的静态方法，所有被private修饰的私有方法、被final 修饰的禁止子类覆盖的方法都会被编译成invokestatic指令。另外所有类的初始化方法< init >和< clinit >会被编译成invokespecial指令。JVM会采用静态绑定机制来顺利的调用这些方法。动态绑定在运行时根据具体对象的类型进行绑定。若一种语言实现了后期绑定，同时必须提供一些机制，可在运行期间判断对象的类型，并分别调用适当的方法。也就是说，编译器此时依然不知道对象的类型，但方法调用机制能自己去调查，找到正确的方法主体。不同的语言对后期绑定的实现方法是有所区别的。但我们至少可以这样认为：它们都要在对象中安插某些特殊类型的信息。看如下Java代码：package hr.test; //被调用的父类 class Father{ public void f1(){ System.out.println("father-f1()"); } public void f1(int i){ System.out.println("father-f1() para-int "+i); } } //被调用的子类 class Son extends Father{ public void f1(){ //覆盖父类的方法 System.out.println("Son-f1()"); } public void f1(char c){ System.out.println("Son-s1() para-char "+c); } } //调用方法 import hr.test.*; public class AutoCall{ public static void main(String[] args){ Father father=new Son(); //多态 father.f1(); //打印结果： Son-f1() } }上面的源代码中有三个重要的概念：多态(polymorphism) 、方法覆盖 、方法重载…