@Lazy注解原理

目录

      • @Lazy作用在类上
      • @Lazy注解作用在字段上
      • @Lazy注解标记的字段或方法中的参数何时触发加载
      • AOP代理中的TargetSource对象
      • 为什么使用了 @Lazy 之后,就能解决循环依赖问题,正常启动了呢?
      • 案例
      • @Resource对@Lazy注入的处理

参考: https://blog.csdn.net/wang489687009/article/details/120577472
参考: https://blog.csdn.net/qq_18297675/article/details/103267125

@Lazy作用在类上

比如下面代码

@Service
@Lazy
public class A {}

Spring注册bean的流程如下:

refresh -> finishBeanFactoryInitialization -> preInstantiateSingletons

@Override
public void preInstantiateSingletons() throws BeansException {// 省略其他代码// Iterate over a copy to allow for init methods which in turn register new bean definitions.// While this may not be part of the regular factory bootstrap, it does otherwise work fine.List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);// 开始实例化所有非懒加载的类的// Trigger initialization of all non-lazy singleton beans...for (String beanName : beanNames) {RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {if (isFactoryBean(beanName)) {Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);// 省略其他代码}else {getBean(beanName);}}}// 省略其他代码
}

if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit())条件表示,不是抽象类并且是单例,且不是懒加载的bean(类上面没有标记@Lazy注解),就走getBean方法的逻辑去创建bean,否则什么也不会做,即@Lazy注解作用在类上,在Spring容器刷新时是不会去创建该bean的

@Lazy注解作用在字段上

比如

@Service
public class B {@Autowired@Lazyprivate A a;}

此时注册B的bean,由于if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit())这个条件都符合,即默认单例不是抽象类,类上面也没有加@Lazy注解,所以此时会走getBean的流程,如下:

getBean -> doGetBean -> createBean -> doCreateBean

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)throws BeanCreationException {// Instantiate the bean.BeanWrapper instanceWrapper = null;if (mbd.isSingleton()) {instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);}if (instanceWrapper == null) {instanceWrapper = createBeanInstance(beanName, mbd, args);}// 省略其他代码// Initialize the bean instance.Object exposedObject = bean;try {populateBean(beanName, mbd, instanceWrapper);exposedObject = initializeBean(beanName, exposedObject, mbd);}catch (Throwable ex) {// 省略其他代码}// 省略其他代码return exposedObject;
}

在调用createBeanInstance方法创建B的bean之后,接着会调用populateBean方法填充属性,即会去注入A这个bean,流程如下:

populateBean -> AutowiredAnnotationBeanPostProcessor#postProcessProperties -> AutowiredFieldElement#inject -> DefaultListableBeanFactory#resolveDependency

public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {// 省略其他代码else {Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(descriptor, requestingBeanName);if (result == null) {// 如果该字段没有被@Lazy注解标注,那么就会通过这个方法去创建该beanresult = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);}return result;}
}

重点关注ContextAnnotationAutowireCandidateResolver#getLazyResolutionProxyIfNecessary方法的实现如下:

	public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, @Nullable String beanName) {return (isLazy(descriptor) ? buildLazyResolutionProxy(descriptor, beanName) : null);}

先判断这个字段是否标记了@Lazy

protected boolean isLazy(DependencyDescriptor descriptor) {for (Annotation ann : descriptor.getAnnotations()) {Lazy lazy = AnnotationUtils.getAnnotation(ann, Lazy.class);if (lazy != null && lazy.value()) {return true;}}MethodParameter methodParam = descriptor.getMethodParameter();if (methodParam != null) {Method method = methodParam.getMethod();if (method == null || void.class == method.getReturnType()) {Lazy lazy = AnnotationUtils.getAnnotation(methodParam.getAnnotatedElement(), Lazy.class);if (lazy != null && lazy.value()) {return true;}}}return false;}

如果标记了,那么执行buildLazyResolutionProxy方法

// 延迟bean的创建,为bean提前创建一个代理对象返回,并不会真正的创建bean对象
protected Object buildLazyResolutionProxy(final DependencyDescriptor descriptor, final @Nullable String beanName) {Assert.state(getBeanFactory() instanceof DefaultListableBeanFactory,"BeanFactory needs to be a DefaultListableBeanFactory");final DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) getBeanFactory();//TargetSource ts = new TargetSource() {@Overridepublic Class<?> getTargetClass() {return descriptor.getDependencyType();}@Overridepublic boolean isStatic() {return false;}// 当第一次调用该代理对象的方法时,才会走getTarget方法里面的doResolveDependency方法去创建真正的bean对象// 如果是JDK生成的代理对象,具体调用在JdkDynamicAopProxy#invoke方法中@Overridepublic Object getTarget() {Object target = beanFactory.doResolveDependency(descriptor, beanName, null, null);if (target == null) {Class<?> type = getTargetClass();if (Map.class == type) {return Collections.emptyMap();}else if (List.class == type) {return Collections.emptyList();}else if (Set.class == type || Collection.class == type) {return Collections.emptySet();}throw new NoSuchBeanDefinitionException(descriptor.getResolvableType(),"Optional dependency not present for lazy injection point");}return target;}@Overridepublic void releaseTarget(Object target) {}};ProxyFactory pf = new ProxyFactory();// 将ts保存到代理工厂中pf.setTargetSource(ts);Class<?> dependencyType = descriptor.getDependencyType();if (dependencyType.isInterface()) {pf.addInterface(dependencyType);}// 创建一个代理对象来注入return pf.getProxy(beanFactory.getBeanClassLoader());}

综上,@Lazy注解标注的字段并不会去触发依赖bean的加载,而是提前生成一个代理bean对象返回,并会在第一次调用代理对象的方法时触发bean的加载

同理,如果@Lazy标记的是方法中的参数也是类似逻辑,只不过链路逻辑为:

populateBean -> AutowiredAnnotationBeanPostProcessor#postProcessProperties -> AutowiredMethodElement#inject -> DefaultListableBeanFactory#resolveDependency

@Lazy注解标记的字段或方法中的参数何时触发加载

还是以刚刚的buildLazyResolutionProxy方法源码说明,先来看看TargetSource对象,是如何起作用的

  1. 首先new了一个ProxyFactory 对象,然后把TargetSource对象设置进去,其实是保存在其父类的targetSource 字段中的

在这里插入图片描述

  1. 执行pf.getProxy(beanFactory.getBeanClassLoader()) 方法
public Object getProxy(@Nullable ClassLoader classLoader) {return createAopProxy().getProxy(classLoader);}
  1. 执行createAopProxy方法
protected final synchronized AopProxy createAopProxy() {if (!this.active) {activate();}return getAopProxyFactory().createAopProxy(this);}
  1. 执行createAopProxy(this)方法,并把pf这个对象作为参数传进去,用advised 字段保存
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {Class<?> targetClass = config.getTargetClass();if (targetClass == null) {throw new AopConfigException("TargetSource cannot determine target class: " +"Either an interface or a target is required for proxy creation.");}// 如果代理的是接口,那么就生成一个JdkDynamicAopProxy对象,并把ProxyFactory 对象传进去,注意它持有TargetSource对象的引用if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {return new JdkDynamicAopProxy(config);}// 否则生成一个ObjenesisCglibAopProxy对象,此种方式是cglib代理实现return new ObjenesisCglibAopProxy(config);}else {return new JdkDynamicAopProxy(config);}}
  1. 以JDK动态代理为例,上述返回JdkDynamicAopProxy对象之后,接着回到第二步,调用JdkDynamicAopProxy#getProxy(classLoader)方法
public Object getProxy(@Nullable ClassLoader classLoader) {if (logger.isTraceEnabled()) {logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());}// 获取要代理的目标类的所有接口Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);// 生成一个代理对象返回,并且this是当前执行getProxy方法的对象即JdkDynamicAopProxy对象return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);}

至此,pf.getProxy(beanFactory.getBeanClassLoader()) 方法生成代理对象的逻辑走完了,接下来看一下这个生成的代理对象第一次调用方法时的逻辑

因为JdkDynamicAopProxy实现了InvocationHandler接口,所以当第一次调用方法时,最终会执行JdkDynamicAopProxy的invoke方法

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object oldProxy = null;boolean setProxyContext = false;// 获取之前保存的ProxyFactory对象,同时获取该对象中的TargetSource对象TargetSource targetSource = this.advised.targetSource;Object target = null;try {// 省略部分代码Object retVal;// 以前好奇为什么通过AopContext#currentProxy()方法能获取到代理对象从而解决自调用导致事务失效的问题// 原来通过@EnableAspectJAutoProxy(exposeProxy = true)注解来实现的,当exposeProxy 为true时,就会走下面的方法if (this.advised.exposeProxy) {// 把代理对象设置进去,底层是一个ThreadLocalMap实现(ThreadLocal<Object> currentProxy = new NamedThreadLocal<>("Current AOP proxy"))// 后续当前调用线程就能愉快的在执行目标方法时获取到当前执行方法的代理对象了oldProxy = AopContext.setCurrentProxy(proxy);setProxyContext = true;}// 重点,这里就调用了targetSource的getTarget方法,前面已经介绍过,它会调用doResolveDependency去加载真正的bean,并把bean放入一级缓存中,后续再调用从一级缓存中获取即可target = targetSource.getTarget();Class<?> targetClass = (target != null ? target.getClass() : null);// 根据方法对象去获取AOP方法拦截器链,里面会进行优化,这里就不展开讲了,大致流程如下:// 1.创建一个MethodCacheKey对象,该对象持有method对象的引用,然后根据MethodCacheKey对象去缓存中获取增强器链// 2.如果获取不到就去advisorChainFactory工厂中获取// 3.把获取到的增强器适配成方法拦截器,然后添加到方法拦截器链中// 4.把MethodCacheKey对象作为key、方法拦截器链作为value存进Map<MethodCacheKey, List<Object>> methodCache中,下次可以根据MethodCacheKey直接从缓存中获取List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);if (chain.isEmpty()) {// 如果方法拦截器链为空,直接反射执行目标bean对象的方法Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);}else {// 否则创建一个MethodInvocation对象来执行目标方法,里面会根据方法拦截器链的顺序来执行,具体就不展开讲了MethodInvocation invocation =new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);retVal = invocation.proceed();}// 省略部分代码return retVal;}finally {if (target != null && !targetSource.isStatic()) {// Must have come from TargetSource.targetSource.releaseTarget(target);}if (setProxyContext) {AopContext.setCurrentProxy(oldProxy);}}}

综上,当一次调用@Lazy注解生成的代理对象方法时,里面会执行targetSource.getTarget()方法,并在第一次执行完成之后会把真实对象放入一级缓存中,后续再次调用直接从一级缓存中获取即可。

AOP代理中的TargetSource对象

经过前面分析,我们知道代理对象执行方法时,都会去调用targetSource.getTarget()方法,那AOP代理生成的代理对象中的targetSource对象又有什么不同呢?

没有发生循环依赖时,AOP动态代理是在bean初始化之后通过AnnotationAwareAspectJAutoProxyCreator 后置处理器生成的,我们来看下它的流程

getBean->doGetBean-> createBean -> doCreateBean -> initializeBean -> applyBeanPostProcessorsAfterInitialization > AnnotationAwareAspectJAutoProxyCreator#postProcessAfterInitialization

  1. postProcessAfterInitialization方法
// AnnotationAwareAspectJAutoProxyCreator 继承父类AbstractAutoProxyCreator的方法
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {if (bean != null) {Object cacheKey = getCacheKey(bean.getClass(), beanName);if (this.earlyProxyReferences.remove(cacheKey) != bean) {// 生成代理对象的逻辑return wrapIfNecessary(bean, beanName, cacheKey);}}return bean;}
  1. wrapIfNecessary方法
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {return bean;}if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {return bean;}if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;}// 获取bean的环绕通知Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);if (specificInterceptors != DO_NOT_PROXY) {this.advisedBeans.put(cacheKey, Boolean.TRUE);// 如果通知不为空,说明要为bean创建代理对象,注意这个new SingletonTargetSource(bean)就是AOP代理生成的targetSource对象Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));this.proxyTypes.put(cacheKey, proxy.getClass());return proxy;}this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;}

看下SingletonTargetSource类的代码

可以看到getTarget方法只是返回了目标对象

public class SingletonTargetSource implements TargetSource, Serializable {private static final long serialVersionUID = 9031246629662423738L;private final Object target;public SingletonTargetSource(Object target) {Assert.notNull(target, "Target object must not be null");this.target = target;}@Overridepublic Class<?> getTargetClass() {return this.target.getClass();}@Overridepublic Object getTarget() {return this.target;}@Overridepublic void releaseTarget(Object target) {// nothing to do}@Overridepublic boolean isStatic() {return true;}// 省略部分代码}
  1. createProxy方法
protected Object createProxy(Class<?> beanClass, @Nullable String beanName,@Nullable Object[] specificInterceptors, TargetSource targetSource) {if (this.beanFactory instanceof ConfigurableListableBeanFactory) {AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);}ProxyFactory proxyFactory = new ProxyFactory();proxyFactory.copyFrom(this);if (!proxyFactory.isProxyTargetClass()) {if (shouldProxyTargetClass(beanClass, beanName)) {proxyFactory.setProxyTargetClass(true);}else {evaluateProxyInterfaces(beanClass, proxyFactory);}}Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);// 将增强器链保存到代理工厂对象中,后续执行代理对象方法时,会根据目标对象的method去获取对应的增强器链,并把增强器链转换成方法拦截器链,然后保存到缓存中(前面有说过这部分逻辑)proxyFactory.addAdvisors(advisors);// 将targetSource对象保存到代理工厂对象中proxyFactory.setTargetSource(targetSource);customizeProxyFactory(proxyFactory);proxyFactory.setFrozen(this.freezeProxy);if (advisorsPreFiltered()) {proxyFactory.setPreFiltered(true);}// 前面讲过这块逻辑,就是创建一个代理对象,并且该代理对象还会持有proxyFactory对象引用(JdkDynamicAopProxy、ObjenesisCglibAopProxy)return proxyFactory.getProxy(getProxyClassLoader());}

综上,可以看到AOP动态代理生成的targetSource对象是SingletonTargetSource对象,它的getTarget方法在每次调用时都会返回目标对象;而@Lazy注解生成的targetSource对象,第一次调用getTarget方法时会去加载目标bean对象,之后每次调用getTarget方法都会去从一级缓存中获取。

为什么使用了 @Lazy 之后,就能解决循环依赖问题,正常启动了呢?

Spring 默认为我们解决了常规场景下的循环依赖问题。但是有些特殊的场景下的循环依赖,Spring 默认是没有解决的。主要的场景如下:

  • prototype 类型的循环依赖
  • constructor 注入的循环依赖
  • @Async 类型的 Bean 的循环依赖

这些特殊的场景,我们都可以通过 @Lazy 来解决。
@Lazy 的本质就是将注入的依赖变成了一个代理对象。使用 @Lazy 时,不会触发依赖 bean 的加载。

假设代码如下:

@Service
public class A {private B b;public A(@Lazy B b) {this.b = b;}
}
@Service
public class B {private A a;public B(A a) {this.a = a;}
}

假设 A 先加载,在创建 A 的实例时,会触发依赖属性 B 的加载,在加载 B 时发现它是一个被 @Lazy 标记过的属性。那么,就不会去直接加载 B,而是产生了一个代理对象注入到了 A 中,这样 A 就能正常的初始化完成放入一级缓存了。B 加载时,再去注入 A 就能直接从一级缓存中获取到 A,这样 B 也能正常初始化完成了。所以,循环依赖的问题就解决了。后续A中调用B的方法时,由于注入的B是代理对象,所以会去走获取bean的流程,因为前面已经注册了B的bean,所以可以直接从一级缓存中取到B的bean。

案例

如下代码能正常启动吗

@Service
@Lazy
public class B {@Autowiredprivate A a;}
@Service
public class A {@Autowiredprivate B b;@Asyncpublic void test() {}
}

结果:启动失败,报错

org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Bean with name 'a' 
has been injected into other beans [b] in its raw version as part of a circular reference, but has eventually been wrapped. 
This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider 
using 'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.

分析:

之前分析过,@Lazy加载类上面,Spring刷新时是不会加载该bean的,所以B这个bean在容器刷新时不会去创建;

  1. 当创建A这个bean时,发现自己依赖了B,所以去加载B这个bean
  2. B从三级缓存中获取A的早期引用,因为A有@Async注解标注的方法,是不会提前生成代理对象的,所以B获取的早期引用是A的初始bean对象
  3. 此时B初始化完成之后,A接着执行初始化时会由AbstractAdvisingBeanPostProcessor后置处理器生成一个代理对象返回
  4. 此时由于B引用了A的初始对象,和最终生成的A代理对象不一致,所以Spring检查出来并提示报错信息

在这里插入图片描述

解决方法:

  • 把Lazy注解放到字段上
  • 或者把Lazy注解放到@Async标注方法的类上
@Service
public class B {@Autowiredprivate A a;}
@Service
@Lazy
public class A {@Autowiredprivate B b;@Asyncpublic void test() {}
}

分析

为什么这样就能启动起来

  1. 因为A类标注@Lazy注解,所以A在Spring刷新容器时不会去加载
  2. 加载B的bean时,发现自己依赖了A,所以去创建A的bean
  3. 加载A的bean时,发现自己依赖了B,所以能从三级缓存中获取B的早期引用,接着初始化完成之后,为A生成一个代理对象
  4. 因为A没有其他类引用着,所以在获取A的早期引用时是null的,不会走之前的分支,把代理A对象放进一级缓存,同时返回代理A对象给到B,B拿到A的代理对象之后,最终执行完初始化操作放入一级缓存中

在这里插入图片描述

@Resource对@Lazy注入的处理

@Resource 注入时是通过ResourceElement类来处理的。且注入时会处理@Lazy的逻辑,相应的代码如下:

// CommonAnnotationBeanPostProcessor.ResourceElement#getResourceToInject()
protected Object getResourceToInject(Object target, String requestingBeanName) {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() {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);
}

具体逻辑和上述的@Autowire对@Lazy注解标注的bean注入逻辑类似,这里不在分析

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

微服务——服务保护(Sentinel)(一)

1.雪崩问题 级联失败或雪崩问题指的是在微服务架构中&#xff0c;由于服务间的相互依赖和调用&#xff0c;当一个服务出现故障时&#xff0c;会引起调用它的服务也出现故障&#xff0c;进而引发整个调用链路的多个服务都出现故障&#xff0c;最终导致整个系统崩溃的现象。 产生…

【笔记】Dynamic Taint Analysis 动态污点分析

Dynamic Taint Analysis 动态污点分析 什么是动态污点分析&#xff1f;为什么要搞动态污点分析&#xff1f; “污点”指的是什么&#xff1f; DTA中的“污点”指代的是不可信的输入&#xff0c;比如用户输入、网络请求、文件数据等。比方说&#xff0c;如果把程序看作一个城市&…

使用 Visily.ai 进行应用界面设计

在现代应用开发中&#xff0c;快速创建高保真线框图和原型是一个巨大的优势。Visily.ai 是一个利用人工智能帮助你实现这一目标的在线工具。本文将介绍如何使用 Visily.ai 进行应用界面设计。 什么是 Visily.ai&#xff1f; Visily.ai 是一个 AI 驱动的 UI 设计工具&#xff…

嵌入式硬件工程师与嵌入式软件工程师的区别(详细版)

嵌入式硬件工程师与嵌入式软件工程师的区别&#xff08;详细版&#xff09; 这里写目录标题 嵌入式硬件工程师与嵌入式软件工程师的区别&#xff08;详细版&#xff09;什么是嵌入式硬件工程师&#xff1f;什么是嵌入式软件工程师&#xff1f;嵌入式硬件工程师与嵌入式软件工程…

css 下拉框展示:当hover的时候展示下拉框 z-index的用法解释

代码如下&#xff1a; <template><div class"outer"><div class"left"></div><div class"aTest2"><div class"box">显示方框</div><div class"aTest3"></div></…

【SQL】指定时间段的下单产品

目录 语法 需求 示例 分析 代码 语法 SUM(column_name) SUM 是一个聚合函数&#xff08;Aggregate Function&#xff09;&#xff0c;用于计算数字列中值的总和。当你需要对表中的某一列数值进行求和时&#xff0c;SUM 函数就显得非常有用。它通常与 GROUP BY 语句一起使用…

运算符两边的数据类型

6-3 类型转换 1.非赋值运算的类型转换 &#xff08;1&#xff09;水平方向的转换&#xff1a;所有的char型和short型自动地转换成int 型&#xff0c;所有的unsigned short 型自动地转换成unsigned型&#xff0c;所有的long型自动地转换成unsigned long 型&#xff0c;所有的f…

exBase

1.准备工作 1.端口配置 下列为默认端口号&#xff0c;若部分端口号已被占用&#xff0c;用户可以根据实际情况进行修改。 端口号 说明 31030 exBase默认端口 31003 配置库默认端口 2181 zookeeper默认端口 9092 kafka默认端口 8091 metaNode的RPC端口 8092 node…

毕业论文写作全攻略,让你轻松过关!

姐妹们&#xff0c;毕业论文是大学旅程的最后一站&#xff0c;也是展示我们学术成果的重要时刻。但是&#xff0c;毕业论文该怎么写呢&#xff1f;别担心&#xff0c;我来告诉你&#xff01;&#x1f4da; writehelp智能写作辅导&#xff1a;http://www.writehelp.vip/?sid17…

线性基学习DAY2

今天是第二题学习线性基&#xff0c;让我对线性基的认识更多了&#xff0c;线性基其实就是去处理整个区间异或最值问题的 我们来看一下昨天的一道题 P4570 [BJWC2011] 元素 昨天其实这题我尝试了两次&#xff0c;一种是普通消元去求解&#xff0c;另一种是高斯消元去求解&…

异地如何进行跨地区协作传输文件?

跨区域协作现在是很多企业的常态了&#xff0c;无论是跨国公司还是国内多地区运营的企业&#xff0c;高效、可靠的文件传输协作都是业务顺利进行的关键。然而&#xff0c;异地传输文件常常面临诸多挑战&#xff0c;如何选择合适的工具和服务成为企业必须考虑的问题。 异地传输文…

【ADC】ΔΣ ADC 中数字滤波器的延迟以及 SAR ADC 与 ΔΣ ADC 的差异对比总结

本文学习于TI 高精度实验室课程&#xff0c;深入探讨 delta-sigma 转换器中使用的数字滤波器。具体来说&#xff0c;本文将重点介绍数字滤波器如何引入延迟&#xff0c;因为这是 SAR 和 delta-sigma ADC 之间的显著差异。 文章目录 一、低延迟数字滤波器二、高延迟数字滤波器三…

妙手上线TikTok Shop组包预报功能,助力全球跨境店卖家大促快速发货!

众所周知&#xff0c;每年的Q4可以说是所有东南亚跨境卖家的旺季&#xff0c;10月起&#xff0c;各种促销活动如10.10品牌大促和双十一大促等接踵而至&#xff0c;为卖家们带来了新的增长机遇。 特别是TikTok Shop这个新兴平台&#xff0c;更是充满无限潜力&#xff0c;根据数…

AIGAME的核心技术竞争力与未来生态规划

AIGAME凭借其领先的区块链和人工智能技术&#xff0c;打造了全球首个融合链游、DeFi和加密聊天的Web3娱乐平台。平台的核心技术创新和多元化生态规划&#xff0c;将推动全球虚拟资产管理和娱乐行业的变革。 AIGAME的核心技术竞争力源于其对区块链和人工智能&#xff08;AI&…

基于nodejs+vue的农产品销售管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码 精品专栏&#xff1a;Java精选实战项目…

队列的基本概念以及模拟使用

1.队列的概念&#xff1a; 只允许在一端进行插入数据操作&#xff0c;在另一端进行删除数据操作的线性表&#xff0c;队列具有先进先出FIFO 入队列 :进行插入操作的一端称为队尾. 出队列:进行删除操作的一端称为队头。 图例如下&#xff1a; 2.Queue是一个接口&…

探索SQlmap:AI驱动的SQL注入神器

文章目录 探索SQLmap&#xff1a;AI驱动的SQL注入神器1. 背景介绍2. 什么是sqlmap&#xff1f;3. 如何安装sqlmap&#xff1f;4. 简单函数使用方法4.1 检测SQL注入4.2 获取数据库列表4.3 读取数据库中的表4.4 转储表中的数据4.5 获取当前用户信息 5. 场景应用5.1 网站后台数据泄…

C++_24_适配器

A 函数对象 概念&#xff1a; ​ 重载函数调用运算符的类实例化的对象&#xff0c;就叫函数对象.又名仿函数,函数对象和&#xff08;)触发重载函数调用运算符的执行。 作用&#xff1a; ​ 为算法提供策略 示例&#xff1a; #include <iostream> using namespace s…

刷题学习日记 (1) - SWPUCTF

写这篇文章主要是想看看自己一个下午能干啥&#xff0c;不想老是浪费时间了&#xff0c;所以刷多少题我就会写多少题解&#xff0c;使用nss随机刷题&#xff0c;但是今天下午不知道为啥一刷都是SWPUCTF的。 [SWPUCTF 2021 新生赛]gift_F12 控制台ctrlf搜索flag即可&#xff0…

【入门01】arcgis api 4.x 创建地图、添加图层、添加指北针、比例尺、图例、卷帘、图层控制、家控件(附完整源码)

1.效果 2.代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title></title><link rel"s…