Spring源码解析(八):bean后置处理器CommonAnnotationBeanPostProcessor

Spring源码系列文章

Spring源码解析(一):环境搭建

Spring源码解析(二):bean容器的创建、默认后置处理器、扫描包路径bean

Spring源码解析(三):bean容器的刷新

Spring源码解析(四):单例bean的创建流程

Spring源码解析(五):循环依赖

Spring源码解析(六):bean工厂后置处理器ConfigurationClassPostProcessor

Spring源码解析(七):bean后置处理器AutowiredAnnotationBeanPostProcessor

Spring源码解析(八):bean后置处理器CommonAnnotationBeanPostProcessor


目录

  • 一、CommonAnnotationBeanPostProcessor简介
  • 二、postProcessMergedBeanDefinition(查询注解信息)
    • 1、查询@PostConstruct和@PreDestroy注解
    • 2、查询@Resource注解
    • 3、checkConfigMembers()方法的作用
  • 三、postProcessProperties(属性填充)
    • 1、inject 执行注入
    • 2、getResourceToInject() 获取注入的值
  • 四、postProcessBeforeInitialization(执行初始化方法)
  • 五、postProcessBeforeDestruction(执行销毁方法)

一、CommonAnnotationBeanPostProcessor简介

主要类图如下:

在这里插入图片描述

  • MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition
    • 查找bean的@Resource属性和@PostConstruct和@PreDestroy方法并缓存起来
  • InstantiationAwareBeanPostProcessor#postProcessProperties
    • @Resource注解属性填充
  • BeanPostProcessor#postProcessBeforeInitialization
    • 初始化前执行解析@PostConstruct注解的初始化方法
  • DestructionAwareBeanPostProcessor#postProcessBeforeDestruction
    • 销毁前执行解析@PreDestroy主键的销毁方法

二、postProcessMergedBeanDefinition(查询注解信息)

  • 执行时机:实例化之后会调用所有MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition方法
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName){//查询@PostConstruct和@PreDestroy`注解并缓存super.postProcessMergedBeanDefinition(beanDefinition, beanType, beanName);//查询@Resource注解并缓存 InjectionMetadata metadata = findResourceMetadata(beanName, beanType, null);metadata.checkConfigMembers(beanDefinition);
}

1、查询@PostConstruct和@PreDestroy注解

  • 先从缓存lifecycleMetadataCache中获取
  • 找不到则通过buildLifecycleMetadata构建生命周期元数据,并放入缓存
  • 生命周期元数据,即初始化销毁方法的元数据
private LifecycleMetadata findLifecycleMetadata(Class<?> clazz) {// 先查缓存中有没有初始化销毁方法元数据// lifecycleMetadataCache是一个map集合,它的key就是当前类的clazz// value是当前类初始化销毁方法元数据if (this.lifecycleMetadataCache == null) {// 如果缓存中没有,则去查询注解构建对应元数据return buildLifecycleMetadata(clazz);}// Quick check on the concurrent map first, with minimal locking.LifecycleMetadata metadata = this.lifecycleMetadataCache.get(clazz);if (metadata == null) {synchronized (this.lifecycleMetadataCache) {metadata = this.lifecycleMetadataCache.get(clazz);if (metadata == null) {// 如果缓存中没有,则去查询注解构建对应元数据metadata = buildLifecycleMetadata(clazz);//放入缓存,key就是当前类的clazzthis.lifecycleMetadataCache.put(clazz, metadata);}return metadata;}}return metadata;
}

CommonAnnotationBeanPostProcessor的构造方法

在这里插入图片描述

构建生命周期元数据

  • AnnotationUtils.isCandidateClass()是判断clazz中是否存在@PostConstruct@PreDestroy注解
  • 遍历clazz父类的所有方法,获取@PostConstruct和@PreDestroy注解信息
    • 都将method封装成LifecycleElement
    • 分别放入initMethods和destroyMethods集合中
private LifecycleMetadata buildLifecycleMetadata(final Class<?> clazz) {/*** this.initAnnotationType为PostConstruct.class* this.destroyAnnotationType为PreDestroy.class* 在CommonAnnotationBeanPostProcessor默认的构造方法中赋值* AnnotationUtils.isCandidateClass()是判断clazz中是否存在PostConstruct和PreDestroy注解*/if (!AnnotationUtils.isCandidateClass(clazz, Arrays.asList(this.initAnnotationType, this.destroyAnnotationType))) {//不存在PostConstruct和PreDestroy注解,直接返回一个空的生命周期元数据return this.emptyLifecycleMetadata;}List<LifecycleElement> initMethods = new ArrayList<>();List<LifecycleElement> destroyMethods = new ArrayList<>();Class<?> targetClass = clazz;do {final List<LifecycleElement> currInitMethods = new ArrayList<>();final List<LifecycleElement> currDestroyMethods = new ArrayList<>();/*** ReflectionUtils.doWithLocalMethods()方法很简单* 遍历targetClass所有的方法,将它作为参数回调接口方法*/ReflectionUtils.doWithLocalMethods(targetClass, method -> {/****************************处理@PostConstruct注解******************************/         //method.isAnnotationPresent()判断方法上有没有指定的注解(反射的知识)if (this.initAnnotationType != null && method.isAnnotationPresent(this.initAnnotationType)) {//(1)构建LifecycleElementLifecycleElement element = new LifecycleElement(method);//加入到初始化方法集合中currInitMethods.add(element);if (logger.isTraceEnabled()) {logger.trace("Found init method on class [" + clazz.getName() + "]: " + method);}}/****************************处理@PreDestroy注解******************************/             if (this.destroyAnnotationType != null && method.isAnnotationPresent(this.destroyAnnotationType)) {currDestroyMethods.add(new LifecycleElement(method));if (logger.isTraceEnabled()) {logger.trace("Found destroy method on class [" + clazz.getName() + "]: " + method);}}});initMethods.addAll(0, currInitMethods);destroyMethods.addAll(currDestroyMethods);//获取父类,因为父类中也有可能指定了生命周期方法targetClass = targetClass.getSuperclass();}while (targetClass != null && targetClass != Object.class);//返回声明周期元数据return (initMethods.isEmpty() && destroyMethods.isEmpty() ? this.emptyLifecycleMetadata ://(2)构建LifecycleMetadatanew LifecycleMetadata(clazz, initMethods, destroyMethods));
}

2、查询@Resource注解

  • 与查询生命周期元数据一样,先从缓存中获取,找不到构建
private InjectionMetadata findResourceMetadata(String beanName, final Class<?> clazz, @Nullable PropertyValues pvs) {// Fall back to class name as cache key, for backwards compatibility with custom callers.String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());// Quick check on the concurrent map first, with minimal locking.InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);//判断是否需要重新解析clazzif (InjectionMetadata.needsRefresh(metadata, clazz)) {synchronized (this.injectionMetadataCache) {metadata = this.injectionMetadataCache.get(cacheKey);if (InjectionMetadata.needsRefresh(metadata, clazz)) {if (metadata != null) {metadata.clear(pvs);}//解析clazz,构建注入@Resource注解元数据metadata = buildResourceMetadata(clazz);this.injectionMetadataCache.put(cacheKey, metadata);}}}return metadata;
}

CommonAnnotationBeanPostProcessor的静态方法

在这里插入图片描述

构建@Resource元数据

  • 先判断clazz是否存在@Resource注解,没有则返回空对象
  • 静态方法属性上添加@Resource注解会抛异常,添加@Autowired注解则是不处理不报错
  • 忽略注入的方法属性类型:ignoredResourceTypes=“javax.xml.ws.WebServiceContext
  • @Resource注解只能加载单个参数的方法上
private InjectionMetadata buildResourceMetadata(final Class<?> clazz) {//判断clazz是否包含resourceAnnotationTypes中的注解if (!AnnotationUtils.isCandidateClass(clazz, resourceAnnotationTypes)) {return InjectionMetadata.EMPTY;}List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();Class<?> targetClass = clazz;do {final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();/********************************处理属性*********************************/ReflectionUtils.doWithLocalFields(targetClass, field -> {//webServiceRef相关,不用管if (webServiceRefClass != null && field.isAnnotationPresent(webServiceRefClass)) {if (Modifier.isStatic(field.getModifiers())) {throw new IllegalStateException("@WebServiceRef annotation is not supported on static fields");}currElements.add(new WebServiceRefElement(field, field, null));}//ejb相关,不用管else if (ejbClass != null && field.isAnnotationPresent(ejbClass)) {if (Modifier.isStatic(field.getModifiers())) {throw new IllegalStateException("@EJB annotation is not supported on static fields");}currElements.add(new EjbRefElement(field, field, null));}//这里开始处理有@Resource注解的属性了else if (field.isAnnotationPresent(Resource.class)) {//@Resource不能加载静态属性上if (Modifier.isStatic(field.getModifiers())) {throw new IllegalStateException("@Resource annotation is not supported on static fields");}//忽略注入的属性类型:ignoredResourceTypes="javax.xml.ws.WebServiceContext"if (!this.ignoredResourceTypes.contains(field.getType().getName())) {//构建ResourceElement对象加入到currElements集合中//我们看一下ResourceElement的构造方法currElements.add(new ResourceElement(field, field, null));}}});/********************************处理方法*********************************/ReflectionUtils.doWithLocalMethods(targetClass, method -> {//获取桥接方法,你就把它当成一个普通的方法对象Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {return;}//webServiceRef相关,不用管if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {if (webServiceRefClass != null && bridgedMethod.isAnnotationPresent(webServiceRefClass)) {if (Modifier.isStatic(method.getModifiers())) {throw new IllegalStateException("@WebServiceRef annotation is not supported on static methods");}if (method.getParameterCount() != 1) {throw new IllegalStateException("@WebServiceRef annotation requires a single-arg method: " + method);}PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);currElements.add(new WebServiceRefElement(method, bridgedMethod, pd));}//ejb相关,不用管else if (ejbClass != null && bridgedMethod.isAnnotationPresent(ejbClass)) {if (Modifier.isStatic(method.getModifiers())) {throw new IllegalStateException("@EJB annotation is not supported on static methods");}if (method.getParameterCount() != 1) {throw new IllegalStateException("@EJB annotation requires a single-arg method: " + method);}PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);currElements.add(new EjbRefElement(method, bridgedMethod, pd));}//这里开始处理有@Resource注解的方法了else if (bridgedMethod.isAnnotationPresent(Resource.class)) {//@Resource不能加载静态方法上if (Modifier.isStatic(method.getModifiers())) {throw new IllegalStateException("@Resource annotation is not supported on static methods");}//获取方法所有参数的类型Class<?>[] paramTypes = method.getParameterTypes();//@Resource注解只能加载单参数的方法上if (paramTypes.length != 1) {throw new IllegalStateException("@Resource annotation requires a single-arg method: " + method);}//忽略参数名字为javax.xml.ws.WebServiceContext的方法if (!this.ignoredResourceTypes.contains(paramTypes[0].getName())) {/*** 该方法在@Autowired注解原理的时候已经说过了* 就是判断当前方法是不是clazz类某个属性的get或set方法,如果是,就* 返回这个属性的属性描述*/PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);//构建ResourceElement对象加入到currElements集合中currElements.add(new ResourceElement(method, bridgedMethod, pd));}}}});elements.addAll(0, currElements);//接着去找父类的@Resource注解targetClass = targetClass.getSuperclass();}while (targetClass != null && targetClass != Object.class);//构建InjectionMetadatareturn InjectionMetadata.forElements(elements, clazz);
}

ResourceElement的构造方法

  • 获取@Resource注解的name和type属性值
  • name默认值:字段名称/方法去掉set,然后将首字母转小写
  • type如果填写类型,会与注入字段类型做校验
public ResourceElement(Member member, AnnotatedElement ae, @Nullable PropertyDescriptor pd) {super(member, pd);//获取@Resource注解信息Resource resource = ae.getAnnotation(Resource.class);//获取注解name属性值String resourceName = resource.name();//获取注解type属性值Class<?> resourceType = resource.type();/*** this.isDefaultName表示是否使用默认名* 注解name属性值为空的时候,就表示使用默认名* 属性的名字或者方法名截取后的值*/this.isDefaultName = !StringUtils.hasLength(resourceName);//使用默认名if (this.isDefaultName) {//获取属性名或方法名resourceName = this.member.getName();//以set开头的方法if (this.member instanceof Method && resourceName.startsWith("set") && resourceName.length() > 3) {//实际上就是截取方法名,去掉set,然后将首字母转小写resourceName = Introspector.decapitalize(resourceName.substring(3));}}/*** 正常情况name写的什么名字,这里就返回什么*/else if (embeddedValueResolver != null) {resourceName = embeddedValueResolver.resolveStringValue(resourceName);}// resourceType的默认值为Object.class// 此时在@Resource上指定注入类型if (Object.class != resourceType) {// 检查指定的类型resourceType是否匹配属性或方法参数checkResourceType(resourceType);}else {// 没有指定类型,则根据Member获取类型resourceType = getResourceType();}this.name = (resourceName != null ? resourceName : "");this.lookupType = resourceType;//这个忽略,没用过String lookupValue = resource.lookup();this.mappedName = (StringUtils.hasLength(lookupValue) ? lookupValue : resource.mappedName());//@Lazy注解处理Lazy lazy = ae.getAnnotation(Lazy.class);this.lazyLookup = (lazy != null && lazy.value());
}

3、checkConfigMembers()方法的作用

  • 这个方法,查询@Autowired @PostConstruct @PreDestroy @Resource注解元数据后都会调用此方法
  • checkConfigMembers()方法的作用之一是考虑可能存在多个注解同时标注在同一个属性上的情况,避免重复处理
  • 通过将已处理的成员标记为外部管理的配置成员,它确保Spring容器在处理依赖注入时不会重复处理同一个属性
  • 简单理解就是去重,然后将需要处理的数据放入Set<InjectedElement> checkedElements集合中,后续统一处理

三、postProcessProperties(属性填充)

  • 在postProcessProperties 方法中完成了Bean 中@Resource注解的属性填充
  • 上一步postProcessMergedBeanDefinition已经筛选出需要注入的属性放入injectionMetadataCache中
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {//获取@Resource注解的注入元数据,前面已经讲过了InjectionMetadata metadata = findResourceMetadata(beanName, bean.getClass(), pvs);try {//执行注入metadata.inject(bean, beanName, pvs);}catch (Throwable ex) {throw new BeanCreationException(beanName, "Injection of resource dependencies failed", ex);}return pvs;
}

1、inject 执行注入

  • 从checkedElements中拿到所有的属性和方法元数据遍历注入
public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {Collection<InjectedElement> checkedElements = this.checkedElements;Collection<InjectedElement> elementsToIterate =(checkedElements != null ? checkedElements : this.injectedElements);if (!elementsToIterate.isEmpty()) {//遍历获取每个需要被注入的元素(属性或方法)for (InjectedElement element : elementsToIterate) {//无论是属性或方法都封装为ResourceElementelement.inject(target, beanName, pvs);}}
}
  • 设置字段和方法的访问性,强行赋值
    • field.setAccessible(true);
    • method.setAccessible(true);
  • getResourceToInject():获取注入的值
protected void inject(Object target, @Nullable String requestingBeanName, @Nullable PropertyValues pvs)throws Throwable {/*********************************属性******************************/    if (this.isField) {Field field = (Field) this.member;//不需要set方法,直接强行赋值ReflectionUtils.makeAccessible(field);//getResourceToInject(target, requestingBeanName)重点是这个方法field.set(target, getResourceToInject(target, requestingBeanName));}/*********************************方法******************************/        else {if (checkPropertySkipping(pvs)) {return;}try {Method method = (Method) this.member;//不管方法的修饰符,强行执行方法ReflectionUtils.makeAccessible(method);method.invoke(target, getResourceToInject(target, requestingBeanName));}catch (InvocationTargetException ex) {throw ex.getTargetException();}}
}

2、getResourceToInject() 获取注入的值

  • 懒加载注入,创建一个代理对象返回
  • 一般注入,从spring容器中获取beanName对应的bean对象
protected Object getResourceToInject(Object target, @Nullable String requestingBeanName) {/*** (1)懒加载自动注入,创建一个代理对象返回* (2)否则直接去spring容器中获取requestingBeanName对应的bean对象*/return (this.lazyLookup ? buildLazyResourceProxy(this, requestingBeanName) :getResource(this, requestingBeanName));
}

懒加载注入对象

protected Object buildLazyResourceProxy(final LookupElement element, final @Nullable String requestingBeanName) {//创建一个目标资源TargetSource ts = new TargetSource() {@Overridepublic Class<?> getTargetClass() {return element.lookupType;}@Overridepublic boolean isStatic() {return false;}@Overridepublic Object getTarget() {//代理类的目标对象也是getResource方法获取的return getResource(element, requestingBeanName);}@Overridepublic void releaseTarget(Object target) {}};//代理工厂ProxyFactory pf = new ProxyFactory();pf.setTargetSource(ts);//设置接口if (element.lookupType.isInterface()) {pf.addInterface(element.lookupType);}//类加载器ClassLoader classLoader = (this.beanFactory instanceof ConfigurableBeanFactory ?((ConfigurableBeanFactory) this.beanFactory).getBeanClassLoader() : null);//获取代理对象return pf.getProxy(classLoader);
}

普通方式注入对象

protected Object getResource(LookupElement element, @Nullable String requestingBeanName)throws NoSuchBeanDefinitionException {if (StringUtils.hasLength(element.mappedName)) {return this.jndiFactory.getBean(element.mappedName, element.lookupType);}if (this.alwaysUseJndiLookup) {return this.jndiFactory.getBean(element.name, element.lookupType);}//上面两个不用管,jndi相关,没用过if (this.resourceFactory == null) {throw new NoSuchBeanDefinitionException(element.lookupType,"No resource factory configured - specify the 'resourceFactory' property");}//获取匹配的依赖对象return autowireResource(this.resourceFactory, element, requestingBeanName);
}
  • 没有指定@Resource注解中的name属性
    • 从容器中根据默认名获取对应bean
    • 根据默认名找不到,通过类型查找注入,还找不到报错
  • 指定@Resource注解中的name属性
    • 从容器中根据指定名称获取bean,找不到报错
protected Object autowireResource(BeanFactory factory, LookupElement element, @Nullable String requestingBeanName)throws NoSuchBeanDefinitionException {Object resource;Set<String> autowiredBeanNames;String name = element.name;if (factory instanceof AutowireCapableBeanFactory) {AutowireCapableBeanFactory beanFactory = (AutowireCapableBeanFactory) factory;/*** 获取依赖描述* 实际上就是* new LookupDependencyDescriptor((Field) this.member, this.lookupType);* new LookupDependencyDescriptor((Method) this.member, this.lookupType);*/DependencyDescriptor descriptor = element.getDependencyDescriptor();/*** 默认名字,没有指定name* !factory.containsBean(name)成立,容器中没有默认名对应的bean*/if (this.fallbackToDefaultTypeMatch && element.isDefaultName && !factory.containsBean(name)) {autowiredBeanNames = new LinkedHashSet<>();//使用beanFactory解析依赖描述,获取依赖bean对象//该方法上篇文章已经讲过,此处不再赘述,这个方法是核心重点方法,一定要看懂resource = beanFactory.resolveDependency(descriptor, requestingBeanName, autowiredBeanNames, null);if (resource == null) {throw new NoSuchBeanDefinitionException(element.getLookupType(), "No resolvable resource object");}}//按指定名name获取依赖对象else {//本质上就是factory.getBean(name, element.lookupType);resource = beanFactory.resolveBeanByName(name, descriptor);autowiredBeanNames = Collections.singleton(name);}}else {resource = factory.getBean(name, element.lookupType);autowiredBeanNames = Collections.singleton(name);}//注册依赖关系if (factory instanceof ConfigurableBeanFactory) {ConfigurableBeanFactory beanFactory = (ConfigurableBeanFactory) factory;for (String autowiredBeanName : autowiredBeanNames) {if (requestingBeanName != null && beanFactory.containsBean(autowiredBeanName)) {beanFactory.registerDependentBean(autowiredBeanName, requestingBeanName);}}}return resource;
}

四、postProcessBeforeInitialization(执行初始化方法)

  • 执行时机:初始化前其他init初始化方法前执行
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {//获取所有生命周期元数据LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass());try {//执行生命周期初始化方法metadata.invokeInitMethods(bean, beanName);}catch (InvocationTargetException ex) {throw new BeanCreationException(beanName, "Invocation of init method failed", ex.getTargetException());}catch (Throwable ex) {throw new BeanCreationException(beanName, "Failed to invoke init method", ex);}return bean;
}

执行初始化方法

public void invokeInitMethods(Object target, String beanName) throws Throwable {Collection<LifecycleElement> checkedInitMethods = this.checkedInitMethods;Collection<LifecycleElement> initMethodsToIterate =(checkedInitMethods != null ? checkedInitMethods : this.initMethods);if (!initMethodsToIterate.isEmpty()) {for (LifecycleElement element : initMethodsToIterate) {if (logger.isTraceEnabled()) {logger.trace("Invoking init method on bean '" + beanName + "': " + element.getMethod());}//执行LifecycleElement的invoke方法element.invoke(target);}}
}
public void invoke(Object target) throws Throwable {//反射执行生命周期初始化方法ReflectionUtils.makeAccessible(this.method);this.method.invoke(target, (Object[]) null);
}

五、postProcessBeforeDestruction(执行销毁方法)

  • 执行流程与初始化方法一样,就不说了
  • 执行时机:bean容器关闭 context.close();调用

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/27959.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

opencv基础-34 图像平滑处理-双边滤波cv2.bilateralFilter()

双边滤波&#xff08;BilateralFiltering&#xff09;是一种图像处理滤波技术&#xff0c;用于平滑图像并同时保留边缘信息。与其他传统的线性滤波方法不同&#xff0c;双边滤波在考虑像素之间的空间距离之外&#xff0c;还考虑了像素之间的灰度值相似性。这使得双边滤波能够有…

数据结构初阶--二叉树的顺序结构之堆

目录 一.堆的概念及结构 1.1.堆的概念 1.2.堆的存储结构 二.堆的功能实现 2.1.堆的定义 2.2.堆的初始化 2.3.堆的销毁 2.4.堆的打印 2.5.堆的插入 向上调整算法 堆的插入 2.6.堆的删除 向下调整算法 堆的删除 2.7.堆的取堆顶元素 2.8.堆的判空 2.9.堆的求堆的…

[Microsoft][ODBC 驱动程序管理器] 未发现数据源名称并且未指定默认驱动程序

1.今天开发了一套服务程序&#xff0c;使用的是Odbc连接MySql数据库&#xff0c; 在我本机用VS打开程序时&#xff0c;访问一切正常&#xff0c;当发布出来装在电脑上&#xff0c;连接数据库时提示&#xff1a; [Microsoft][ODBC 驱动程序管理器] 未发现数据源名称并且未指定…

VoxWeekly|The Sandbox 生态周报|20230731

欢迎来到由 The Sandbox 发布的《VoxWeekly》。我们会在每周发布&#xff0c;对上一周 The Sandbox 生态系统所发生的事情进行总结。 如果你喜欢我们内容&#xff0c;欢迎与朋友和家人分享。请订阅我们的 Medium 、关注我们的 Twitter&#xff0c;并加入 Discord 社区&#xf…

Zebec Protocol 将进军尼泊尔市场,通过 Zebec Card 推动该地区金融平等

流支付正在成为一种全新的支付形态&#xff0c;Zebec Protocol 作为流支付的主要推崇者&#xff0c;正在积极的推动该支付方案向更广泛的应用场景拓展。目前&#xff0c;Zebec Protocol 成功的将流支付应用在薪酬支付领域&#xff0c;并通过收购 WageLink 将其纳入旗下&#xf…

C#实现SqlServer数据库同步

实现效果&#xff1a; 设计思路&#xff1a; 1. 开启数据库及表的cdc&#xff0c;定时查询cdc表数据&#xff0c;封装sql语句(通过执行类型&#xff0c;主键;修改类型的cdc数据只取最后更新的记录)&#xff0c;添加到离线数据表&#xff1b; 2. 线程定时查询离线数据表&#xf…

有哪些常用的设计素材网站?

素材网站可以是设计师和创意人员的灵感来源。这些网站收集了各种类型的平面设计图片&#xff0c;包括标志、海报、网站设计、包装设计、插图等。在本文中&#xff0c;我将推荐15个平面设计图素材网站&#xff0c;以帮助您找到新的想法和灵感。 1.即时设计资源社区 即时设计资…

SpringBoot 热部署

文章目录 前言一、spring-boot-devtools添加热部署框架支持settings 开启项目自动编译开启运行中热部署使用Debug启动 二、IDEA 自带 HowSwap 功能设置 Spring Boot 启动类等待项目启动完成点击热加载按钮存在的问题 三、JRebel 插件【推荐】安装插件使用插件 前言 在日常开发…

分布式协议与算法——CAP理论、ACID理论、BASE理论

CAP理论 CAP理论&#xff0c;对分布式系统的特性做了高度抽象&#xff0c;比如抽象成了一致性、可用性和分区容错性&#xff0c;并对特性间的冲突&#xff08;也就是CAP不可能三角&#xff09;做了总结。 CAP三指标 CAP理论对分布式系统的特性做了高度抽象&#xff0c;形成了…

BL302嵌入式ARM控制器进行SQLite3数据库操作的实例演示

本文主要讲述了在钡铼技术BL302嵌入式arm控制器上运行 SQLite3 数据库的命令示例。SQLite3 是一个轻型的嵌入式数据库&#xff0c;不需要安装数据库服务器进程&#xff0c;占用资源低且处理速度快。 首先&#xff0c;需要将对应版本的 SQLite3 文件复制到设备的 /usr/ 目录下&…

python之prettytable库的使用

文章目录 一 什么是prettytable二 prettytable的简单使用1. 添加表头2. 添加行3. 添加列4. 设置对齐方式4. 设置输出表格样式5. 自定义边框样式6. 其它功能 三 prettytable在实际中的使用 一 什么是prettytable prettytable是Python的一个第三方工具库&#xff0c;用于创建漂亮…

CI/CD持续集成持续发布(jenkins)

1.背景 在实际开发中&#xff0c;我们经常要一边开发一边测试&#xff0c;当然这里说的测试并不是程序员对自己代码的单元测试&#xff0c;而是同组程序员将代码提交后&#xff0c;由测试人员测试&#xff1b; 或者前后端分离后&#xff0c;经常会修改接口&#xff0c;然后重新…

Makefile

什么是 Makefile 一个工程中的源文件不计其数&#xff0c;其按类型、功能、模块分别放在若干个目录中&#xff0c; Makefile文件定义了一系列的规则来指定哪些文件需要先编译&#xff0c;哪些文件需要后编 译&#xff0c;哪些文件需要重新编译&#xff0c;甚至于进行更复杂的功…

Android平台GB28181设备接入端如何实现多视频通道接入?

技术背景 我们在设计Android平台GB28181设备接入模块的时候&#xff0c;有这样的场景诉求&#xff0c;一个设备可能需要多个通道&#xff0c;常见的场景&#xff0c;比如车载终端&#xff0c;一台设备&#xff0c;可能需要接入多个摄像头&#xff0c;那么这台车载终端设备可以…

使用webpack插件webpack-dev-server 出现Cannot GET/的解决办法

问题描述 文档地址深入浅出webpack 使用 DevServer运行webpack&#xff0c;跑起来之后提示Cannot GET/&#xff1a; 解决方案&#xff1a; 查阅官方文档 根据目录结构修改对应的配置&#xff1a; 然后就可以成功访问&#xff1a;

【MongoDB】万字长文,命令与代码一一对应SpringBoot整合MongoDB之MongoTemplate

目录 一、导入依赖与配置信息 二、导入测试数据创建实体类 三、插入数据 1、Insert默认集合插入 2、Insert指定集合插入 3、Insert批量插入数据 4、save默认集合插入 5、save指定集合插入 6、insert与save的区别 四、修改数据 1、修改符合条件的第一条数据 2、全…

redis 原理 7:开源节流 —— 小对象压缩

Redis 是一个非常耗费内存的数据库&#xff0c;它所有的数据都放在内存里。如果我们不注意节约使用内存&#xff0c;Redis 就会因为我们的无节制使用出现内存不足而崩溃。Redis 作者为了优化数据结构的内存占用&#xff0c;也苦心孤诣增加了非常多的优化点&#xff0c;这些优化…

[PM]敏捷开发之Scrum总结

在项目管理中&#xff0c;不少企业和项目团队也发现传统的项目管理模式已不能很好地适应今天的项目环境的要求。因此&#xff0c;敏捷项目管理应运而生&#xff0c;本文将为大家介绍Scrum敏捷项目管理以及应用方法。 什么是Scrum敏捷项目管理 敏捷项目管理作为新兴的项目管理模…

Java后台生成ECharts图片,并以Base64字符串返回

前言 通过echarts的jar包&#xff0c;Java后台生成一张图片&#xff0c;并把图片插入到word中。关于word插图片的代码在下一章。 需要用到的工具PhantomJS,Echarts-convert.js,jquery.js,echarts.js。 1.PhantomJS 介绍 PhantomJS是一个不需要浏览器的富客户端。 官方介绍&…

[保研/考研机试] 猫狗收容所 C++实现

题目描述&#xff1a; 输入&#xff1a; 第一个是n&#xff0c;它代表操作序列的次数。接下来是n行&#xff0c;每行有两个值m和t&#xff0c;分别代表题目中操作的两个元素。 输出&#xff1a; 按顺序输出收养动物的序列&#xff0c;编号之间以空格间隔。 源代码&#xff…