@Configuration的代理保证bean单例

背景

先看下面问题:

类A、B,类AppConfig是配置类加了@Configuration,并且对A、B加了@Configuration,要把beanA、beanB创建到spring容器中,注意创建B时,调用了getA()。

此时按照代码正常理解逻辑会打印两遍“new A()”,因为调用了两次new A()。但是实际你可以去试试,只打印了一遍。。。这就很奇怪了。。。

是因为spring默认bean是单例的这里不会创建两遍,那么spring是如何保证自己的“单例原则”没有被打破的往下看。。

public class A {

    public A(){
        System.out.println("new A()");
    }
    
}

public class B {

    public B(){
        System.out.println("new B()");
    }
}

@ComponentScan("com.yonghui.yh")
@Configuration
public class AppConfig {

    @Bean
    public A getA(){
      	System.out.print("new A()")
        return new A();
    }

    @Bean
    public B getB(){
        //这里调用了getA()
        getA();
        return new B();
    }
}

public class ApplicationTest {
    public static void main(String[] args) {
        //初始化spring容器
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        // System.out.print("new A()")打印一遍
    }
}

其实源于我们AppConfig类中加了一个@Configuration,这个东西说明该类是一个配置类,spring会对这种加了@Configuration注解的类进行特殊处理,也就是传说中的代理,我们可以在容器启动时,看看这个类,可以发现他被cglib代理了。

AppConfig是一个配置类,任何一个类都可以被指定成为配置类,但是这个类并不一定需要加@Configuration注解,这个注解的作用就是能够是AppConfig能够产生一个代理对象,确保AppConfig类中@Bean创建的bean是单例的,如果没有AppConfig没有@Configuration就不是代理对象,那么出现@Bean方法相互调用会使单例原则被破坏。

public class ApplicationTest {
    public static void main(String[] args) {
        //初始化spring容器
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        System.out.println(context.getBean(AppConfig.class));
        // 因为AppConfig加了@Configuration: com.yonghui.yh.AppConfig$$EnhancerBySpringCGLIB$$4ce7fa7e@75881071
    }
}

有了思路去看看,到底是怎么做的,其实就是在ConfigurationClassPostProcessor bean工厂后置处理器中进行的。这个类实现了BeanDefinitionRegistryPostProcessor、而BeanDefinitionRegistryPostProcessor实现了BeanDefinitionPostProcessor。

(ConfigurationClassPostProcessor 这个处理器听说很重要很重要很重要,是spring开天辟地的五个BeanDefinition之一,我还没看,今天分享的是其中冰山一小🦶🏻,后面在学学)

public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor,
      PriorityOrdered, ResourceLoaderAware, ApplicationStartupAware, BeanClassLoaderAware, EnvironmentAware {

说的这就要看看cglib代理的一个大概流程。

补充

这里的cglib代理原理如下:通过Enhancerr生成了一个继承了A类的子类,并创建对象(代理对象),你可以清楚的看到setSuperclass、setCallback、、、不就是在创建子类嘛,而setCallback方法就是设置一个MethodInterceptor拦截器,“增强”就是在这里面做的。

你可以回顾一下jdk动态代理得区别,可以在梳理梳理。

public class A {
    public void a(){
        System.out.println("aaaa");
    }
}

public class CglibA {
    public static void main(String[] args) {
        MethodInterceptor methodInterceptor = new MethodInterceptor() {
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                System.out.println("cglib before");
                Object invoke = methodProxy.invoke(o, null);
                System.out.println("cglib after");
                return invoke;
            }
        };
        //理解为通过Enhancer生成了一个继承了A类的子类,并创建对象(代理对象)
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(A.class);
        // 这里可以调用setCallbacks方法就传多个增强,行程链式增强(拦截器组)
        enhancer.setCallback(methodInterceptor);
        enhancer.setUseFactory(false);
        enhancer.setCallbackType(methodInterceptor.getClass());
        A a = (A) enhancer.create();
        a.a();
        /*
        打印:
        cglib before
        aaaa
        cglib after
        */
    }

}

解答

回到上面问题一开始的问题“为啥打印一遍”,直接看下源码:(我只截取了关键代码,并附上注释,可以进去点一点,跟着我的注释思路,很清晰的,最好是自己创建一个配置类,debug一哈)

ConfigurationClassPostProcessor实现的接口BeanDefinitionRegistryPostProcessor的父类接口的BeanFactoryPostProcessor的postProcessBeanFactory()

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
   int factoryId = System.identityHashCode(beanFactory);
   if (this.factoriesPostProcessed.contains(factoryId)) {
      throw new IllegalStateException(
            "postProcessBeanFactory already called on this post-processor against " + beanFactory);
   }
   this.factoriesPostProcessed.add(factoryId);
   if (!this.registriesPostProcessed.contains(factoryId)) {
      // BeanDefinitionRegistryPostProcessor hook apparently not supported...
      // Simply call processConfigurationClasses lazily at this point then.
      processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
   }
  
		// 从这开始
   enhanceConfigurationClasses(beanFactory);
   beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}

enhanceConfigurationClasses(beanFactory)

public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
   StartupStep enhanceConfigClasses = this.applicationStartup.start("spring.context.config-classes.enhance");
   Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
   for (String beanName : beanFactory.getBeanDefinitionNames()) {
   		//...省略部分源码.....
      //判断是否是一个全配置类 full
      if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) {
         //...省略部分源码.....
         // 全配置类进行记录,如果是lite非全配置类,那么不用管spring该怎么new就怎么new
         configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
      }
   }
   //无全配置类记录,直接结束
   if (configBeanDefs.isEmpty()) {
      // nothing to enhance -> return immediately
      enhanceConfigClasses.end();
      return;
   }
   if (IN_NATIVE_IMAGE) {
      throw new BeanDefinitionStoreException("@Configuration classes need to be marked as " +
            "proxyBeanMethods=false. Found: " + configBeanDefs.keySet());
   }

   //遍历全配置类configBeanDefs开始代理  
   ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
   for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
      AbstractBeanDefinition beanDef = entry.getValue();
      // If a @Configuration class gets proxied, always proxy the target class
      beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
      // Set enhanced subclass of the user-specified bean class
			//代理逻辑,返回代理类
			Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
			if (configClass != enhancedClass) {
				if (logger.isTraceEnabled()) {
					logger.trace(String.format("Replacing bean definition '%s' existing class '%s' with " +
							"enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName()));
				}
				//设置实际的beanClass为这个代理类,后面用这个bd时,就是使用的这个代理过的类了
				//要去看看,对他增强了什么
				beanDef.setBeanClass(enhancedClass);
			}
   //...省略部分源码.....
}

想知道上面打印一次那个原因,就需要看看,对他增强了什么。ConfigurationClassEnhancer封装了对这个全配置类增强的逻辑(即上面演示的cglib、enhance等,其中增强拦截器就是BeanMethodInterceptor、BeanFactoryAwareMethodInterceptor,主要在BeanMethodInterceptor中)

class ConfigurationClassEnhancer {

   // setCallbacks方法就传多个增强,行程链式增强(拦截器组)
   // BeanMethodInterceptor、BeanFactoryAwareMethodInterceptor是这个类的内部类
   private static final Callback[] CALLBACKS = new Callback[] {
         new BeanMethodInterceptor(),
         new BeanFactoryAwareMethodInterceptor(),
         NoOp.INSTANCE
   };

   private static final ConditionalCallbackFilter CALLBACK_FILTER = new ConditionalCallbackFilter(CALLBACKS);
   private static final String BEAN_FACTORY_FIELD = "$$beanFactory";
   private static final Log logger = LogFactory.getLog(ConfigurationClassEnhancer.class);
   private static final SpringObjenesis objenesis = new SpringObjenesis();

   /**
   * 调用enhancer.enhance(configClass, this.beanClassLoader);
   */
   public Class<?> enhance(Class<?> configClass, @Nullable ClassLoader classLoader) {
      if (EnhancedConfiguration.class.isAssignableFrom(configClass)) {
         if (logger.isDebugEnabled()) {
            logger.debug(String.format("Ignoring request to enhance %s as it has " +
                  "already been enhanced. This usually indicates that more than one " +
                  "ConfigurationClassPostProcessor has been registered (e.g. via " +
                  "<context:annotation-config>). This is harmless, but you may " +
                  "want check your configuration and remove one CCPP if possible",
                  configClass.getName()));
         }
         return configClass;
      }
      Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader));
      if (logger.isTraceEnabled()) {
         logger.trace(String.format("Successfully enhanced %s; enhanced class name is: %s",
               configClass.getName(), enhancedClass.getName()));
      }
      return enhancedClass;
   }

   /**
    * Creates a new CGLIB {@link Enhancer} instance.
    */
   private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) {
      Enhancer enhancer = new Enhancer();
      enhancer.setSuperclass(configSuperClass);
      enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class});
      enhancer.setUseFactory(false);
      enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
      enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
      enhancer.setCallbackFilter(CALLBACK_FILTER);
      enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());
      return enhancer;
   }

   /**
    * Uses enhancer to generate a subclass of superclass,
    * ensuring that callbacks are registered for the new subclass.
    */
   private Class<?> createClass(Enhancer enhancer) {
      Class<?> subclass = enhancer.createClass();
      // Registering callbacks statically (as opposed to thread-local)
      // is critical for usage in an OSGi environment (SPR-5932)...
      Enhancer.registerStaticCallbacks(subclass, CALLBACKS);
      return subclass;
   }


   /**
    * Marker interface to be implemented by all @Configuration CGLIB subclasses.
    * Facilitates idempotent behavior for {@link ConfigurationClassEnhancer#enhance}
    * through checking to see if candidate classes are already assignable to it, e.g.
    * have already been enhanced.
    * <p>Also extends {@link BeanFactoryAware}, as all enhanced {@code @Configuration}
    * classes require access to the {@link BeanFactory} that created them.
    * <p>Note that this interface is intended for framework-internal use only, however
    * must remain public in order to allow access to subclasses generated from other
    * packages (i.e. user code).
    */
   public interface EnhancedConfiguration extends BeanFactoryAware {
   }


   /**
    * Conditional {@link Callback}.
    * @see ConditionalCallbackFilter
    */
   private interface ConditionalCallback extends Callback {

      boolean isMatch(Method candidateMethod);
   }


   /**
    * A {@link CallbackFilter} that works by interrogating {@link Callback Callbacks} in the order
    * that they are defined via {@link ConditionalCallback}.
    */
   private static class ConditionalCallbackFilter implements CallbackFilter {

      private final Callback[] callbacks;

      private final Class<?>[] callbackTypes;

      public ConditionalCallbackFilter(Callback[] callbacks) {
         this.callbacks = callbacks;
         this.callbackTypes = new Class<?>[callbacks.length];
         for (int i = 0; i < callbacks.length; i++) {
            this.callbackTypes[i] = callbacks[i].getClass();
         }
      }

      @Override
      public int accept(Method method) {
         for (int i = 0; i < this.callbacks.length; i++) {
            Callback callback = this.callbacks[i];
            if (!(callback instanceof ConditionalCallback) || ((ConditionalCallback) callback).isMatch(method)) {
               return i;
            }
         }
         throw new IllegalStateException("No callback available for method " + method.getName());
      }

      public Class<?>[] getCallbackTypes() {
         return this.callbackTypes;
      }
   }
		
   private static class BeanMethodInterceptor implements MethodInterceptor, ConditionalCallback {

      /**
       * Enhance a {@link Bean @Bean} method to check the supplied BeanFactory for the
       * existence of this bean object.
       * @throws Throwable as a catch-all for any exception that may be thrown when invoking the
       * super implementation of the proxied method i.e., the actual {@code @Bean} method
       */
      @Override
      @Nullable
      public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
               MethodProxy cglibMethodProxy) throws Throwable {

         ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);
         String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);

         // Determine whether this bean is a scoped-proxy
         if (BeanAnnotationHelper.isScopedProxy(beanMethod)) {
            String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName);
            if (beanFactory.isCurrentlyInCreation(scopedBeanName)) {
               beanName = scopedBeanName;
            }
         }

         // To handle the case of an inter-bean method reference, we must explicitly check the
         // container for already cached instances.

         // First, check to see if the requested bean is a FactoryBean. If so, create a subclass
         // proxy that intercepts calls to getObject() and returns any cached bean instance.
         // This ensures that the semantics of calling a FactoryBean from within @Bean methods
         // is the same as that of referring to a FactoryBean within XML. See SPR-6602.
         if (factoryContainsBean(beanFactory, BeanFactory.FACTORY_BEAN_PREFIX + beanName) &&
               factoryContainsBean(beanFactory, beanName)) {
            Object factoryBean = beanFactory.getBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName);
            if (factoryBean instanceof ScopedProxyFactoryBean) {
               // Scoped proxy factory beans are a special case and should not be further proxied
            }
            else {
               // It is a candidate FactoryBean - go ahead with enhancement
               return enhanceFactoryBean(factoryBean, beanMethod.getReturnType(), beanFactory, beanName);
            }
         }
			/**
			 * 判断是否正在被调用,同理:生命周期循环依赖的正在创建的bean
			 * 这里和循环依赖相似:
			 * 		判断正在调用的方法 和 正在创建bean的方法
			 * 		1、如果	正在调用的方法 和 正在创建bean的方法 相同就会直接调用父类的方法(我们的@Bean方法)进行创建bean
			 * 		2、如果不相同就会先去getBean
			 *	这里不管是哪个方法都会进入代理的这个里面
			 *
			 * 	例如:
			 * 	创建beanA的生命周期中:
			 * 		调用@Bean getA()方法时,此时记录set集合中:getA(),调用代理类的方法也是getA(),就会直接创建beanA,清空set集合中:getA()
			 * 		调用	@Bean getB()方式时,此时记录set集合中:getB(),get()中又调用了getA(),(这里不管是哪个方法都会进入代理的这个里面),所以又会执行到
			 * 		这里的判断当前执行(调用)的是getA(),当时当前创建的却是getB(),就会执行下面的resolveBeanReference(),resolveBeanReference()中会去getBean(),
			 * 		保证了bean的单例,执行完getA(),后继续执行beanB的生命周期
			 *
			 */
         if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
            if (logger.isInfoEnabled() &&
                  BeanFactoryPostProcessor.class.isAssignableFrom(beanMethod.getReturnType())) {
               logger.info(String.format("@Bean method %s.%s is non-static and returns an object " +
                           "assignable to Spring's BeanFactoryPostProcessor interface. This will " +
                           "result in a failure to process annotations such as @Autowired, " +
                           "@Resource and @PostConstruct within the method's declaring " +
                           "@Configuration class. Add the 'static' modifier to this method to avoid " +
                           "these container lifecycle issues; see @Bean javadoc for complete details.",
                     beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName()));
            }
            //如果	正在调用的方法 和 正在创建bean的方法 相同就会直接调用父类的方法进行创建bean
            return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
         }
			  	//如果不相同就会先去getBean
         return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);
      }

      private Object resolveBeanReference(Method beanMethod, Object[] beanMethodArgs,
            ConfigurableBeanFactory beanFactory, String beanName) {

         // The user (i.e. not the factory) is requesting this bean through a call to
         // the bean method, direct or indirect. The bean may have already been marked
         // as 'in creation' in certain autowiring scenarios; if so, temporarily set
         // the in-creation status to false in order to avoid an exception.
         boolean alreadyInCreation = beanFactory.isCurrentlyInCreation(beanName);
         try {
            if (alreadyInCreation) {
               beanFactory.setCurrentlyInCreation(beanName, false);
            }
            boolean useArgs = !ObjectUtils.isEmpty(beanMethodArgs);
            if (useArgs && beanFactory.isSingleton(beanName)) {
               // Stubbed null arguments just for reference purposes,
               // expecting them to be autowired for regular singleton references?
               // A safe assumption since @Bean singleton arguments cannot be optional...
               for (Object arg : beanMethodArgs) {
                  if (arg == null) {
                     useArgs = false;
                     break;
                  }
               }
            }
           //getBean 去获取bean ,没有时就会先去创建那个bean的生命周期
            Object beanInstance = (useArgs ? beanFactory.getBean(beanName, beanMethodArgs) :
                  beanFactory.getBean(beanName));
            if (!ClassUtils.isAssignableValue(beanMethod.getReturnType(), beanInstance)) {
               // Detect package-protected NullBean instance through equals(null) check
               if (beanInstance.equals(null)) {
                  if (logger.isDebugEnabled()) {
                     logger.debug(String.format("@Bean method %s.%s called as bean reference " +
                           "for type [%s] returned null bean; resolving to null value.",
                           beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName(),
                           beanMethod.getReturnType().getName()));
                  }
                  beanInstance = null;
               }
               else {
                  String msg = String.format("@Bean method %s.%s called as bean reference " +
                        "for type [%s] but overridden by non-compatible bean instance of type [%s].",
                        beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName(),
                        beanMethod.getReturnType().getName(), beanInstance.getClass().getName());
                  try {
                     BeanDefinition beanDefinition = beanFactory.getMergedBeanDefinition(beanName);
                     msg += " Overriding bean of same name declared in: " + beanDefinition.getResourceDescription();
                  }
                  catch (NoSuchBeanDefinitionException ex) {
                     // Ignore - simply no detailed message then.
                  }
                  throw new IllegalStateException(msg);
               }
            }
            Method currentlyInvoked = SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod();
            if (currentlyInvoked != null) {
               String outerBeanName = BeanAnnotationHelper.determineBeanNameFor(currentlyInvoked);
               beanFactory.registerDependentBean(beanName, outerBeanName);
            }
            return beanInstance;
         }
         finally {
            if (alreadyInCreation) {
               beanFactory.setCurrentlyInCreation(beanName, true);
            }
         }
      }


}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Matlab论文插图绘制模板第85期—模值赋色的箭头图

在之前的文章中&#xff0c;分享了Matlab箭头图的绘制模板&#xff1a; 进一步&#xff0c;如果我们想对每一个箭头赋上颜色&#xff0c;以更加直观地表示其模值的大小&#xff0c;该怎么操作呢&#xff1f; 那么&#xff0c;来看一下模值赋色的箭头图的绘制模板。 先来看一下…

老胡的周刊(第086期)

老胡的信息周刊[1]&#xff0c;记录这周我看到的有价值的信息&#xff0c;主要针对计算机领域&#xff0c;内容主题极大程度被我个人喜好主导。这个项目核心目的在于记录让自己有印象的信息做一个留存以及共享。&#x1f3af; 项目MochiDiffusion[2]在 MacOS 上运行原生的 Stab…

游戏解密之常见网络游戏同步方式分析

一、为什么需要有同步呢&#xff1f; 同步机制是用来维护游戏的一致性&#xff0c;通俗的说就是虚拟世界中的事实&#xff1b;比如在CF中&#xff0c;大家的PING都很高&#xff0c;A和B两个玩家同时发现了对方&#xff0c;并向对方开火&#xff0c;如果没有很好的同步机制&…

【学习笔记】滑动窗口

acwing.滑动窗口https://www.acwing.com/problem/content/156/ 给定一个大小为 n≤106≤106 的数组。 有一个大小为 k 的滑动窗口&#xff0c;它从数组的最左边移动到最右边。 你只能在窗口中看到 k 个数字。 每次滑动窗口向右移动一个位置。 以下是一个例子&#xff1a; …

【博学谷学习记录】超强总结,用心分享 | 架构师 MySql扩容学习总结

文章目录1. 停机方案2.停写方案3.日志方案4.双写方案&#xff08;中小型数据&#xff09;5.平滑2N方案&#xff08;大数据量&#xff09;1. 停机方案 发布公告 为了进行数据的重新拆分&#xff0c;在停止服务之前&#xff0c;我们需要提前通知用户&#xff0c;比如&#xff1a…

他98年的,我真的玩不过他...

现在的小年轻真的卷得过分了。前段时间我们公司来了个98年的&#xff0c;工作没两年&#xff0c;跳槽到我们公司起薪18K&#xff0c;都快接近我了。后来才知道人家是个卷王&#xff0c;从早干到晚就差搬张床到工位睡觉了。 最近和他聊了一次天&#xff0c;原来这位小老弟家里条…

MySQL 分布式数据库实现:无需修改代码,轻松实现分布式能力

这个项目做什么 ShardingSphere-Proxy&#xff0c;可以让用户像使用原生数据库一样使用 Apache ShardingSphere。 了解一项技术的开始&#xff0c;一般从官网开始。先来看一看官网对 ShardingSphere-Proxy 的定义是什么样的&#xff1a; 定位为透明化的数据库代理端&#xff…

springboot学习2

一、spring boot自动装配原理 pom.xml spring-boot-dependencies 核心依赖在父工程中 在写或者引入一些spring boot依赖的时候&#xff0c;不需要指定版本&#xff0c;因为有这些版本仓库启动器 <dependency><groupId>org.springframework.boot</groupId>&…

会画画的海龟,Python Turtle库详解(27)

小朋友们好&#xff0c;大朋友们好&#xff01; 我是猫妹&#xff0c;一名爱上Python编程的小学生。 欢迎和猫妹一起&#xff0c;趣味学Python。 今日主题 介绍下Python的turtle库&#xff0c;这是一个可以画画的库&#xff0c;非常适合小孩子在屏幕上画画。 先学习基础知…

第08章_面向对象编程(高级)

第08章_面向对象编程(高级) 讲师&#xff1a;尚硅谷-宋红康&#xff08;江湖人称&#xff1a;康师傅&#xff09; 官网&#xff1a;http://www.atguigu.com 本章专题与脉络 1. 关键字&#xff1a;static 回顾类中的实例变量&#xff08;即非static的成员变量&#xff09; c…

虚拟化技术:实现资源高效利用和灵活管理的利器

虚拟化技术是一种通过软件或硬件手段&#xff0c;将物理资源抽象化&#xff0c;从而创建虚拟资源的技术。这种技术可以应用于计算、存储、网络等领域&#xff0c;通过将物理资源划分为多个虚拟资源&#xff0c;使得多个应用程序或用户可以共享同一组物理资源&#xff0c;从而提…

Linux 进程管理之四大名捕

一、四大名捕 四大名捕&#xff0c;最初出现于温瑞安创作的武侠小说&#xff0c;是朝廷中正义力量诸葛小花的四大徒弟&#xff0c;四人各怀绝技&#xff0c;分别是轻功暗器高手 “无情”、内功卓越的高手“铁手”、腿功惊人的“追命” 和剑法一流的“冷血”。 本文四大名捕由…

关于电商商品数据API接口列表,你想知道的(详情页、Sku信息、商品描述、评论问答列表)

目录 一、商品数据API接口列表 二、商品详情数据API调用代码item_get 三、获取sku详细信息item_sku 四、获得淘宝商品评论item_review 五、数据说明文档 进入 一、商品数据API接口列表 二、商品详情数据API调用代码item_get <?php// 请求示例 url 默认请求参数已经URL…

集合-LinkedList

LinkedList LinkedList的概述 LinkedList的底层使用双向链表实现。 链表是一种线性数据结构&#xff0c;其中每个元素都是一个单独的对象&#xff0c;包含一个指向列表中下一个节点的引用。 它可以用于实现各种抽象数据类型&#xff0c;例如列表、堆栈、队列等。 LinkedLis…

Carla仿真二:Carla多视图切换代码详解

文章目录前言一、Carla多视图切换效果二、Camera安装坐标系1、Carla.Location2、Carla.Rotation三、接口及代码详解1、接口介绍2、生成上帝视图代码3、生成Camera视图代码四、完整代码前言 1、Carla提供了大量的Python API接口&#xff0c;用户可以通过查找文档实现各类功能&a…

无限制翻译软件-中英互译字数无限

翻译软件是我们工作及学习中必不可少的工具&#xff0c;然而许多翻译软件在使用时常常会出现字数限制的问题,这使得用户在处理长文本和大量文本时变得十分麻烦。如果你也遇到了类似的问题&#xff0c;那么哪个翻译软件不限制字数将为您带来全新的翻译体验。 以下是我们的哪个翻…

Vite打包后直接使用浏览器打开,显示空白问题

vite打包后&#xff0c;直接用浏览器打开显示空白 1.需求&#xff1a; 安卓webview等浏览器直接打开文件显示 2.原因 &#xff08;1&#xff09;资源路径错误&#xff1a; vite.config.js 配置 base: “./” &#xff08;在webpack中则配置publicPath: "./"即可…

ATTCK v12版本战术实战研究——提权(一)

一、概述 前几期文章中&#xff0c;我们中介绍ATT&CK 14项战术中提权战术&#xff08;一&#xff09;&#xff0c;包括提权前6项子技术。那么从前文中介绍的相关提权技术来开展测试&#xff0c;进行更深一步的分析。本文主要内容是介绍攻击者在运用提权技术时&#xff0c;…

算法 贪心2 || 122.买卖股票的最佳时机II 55. 跳跃游戏 45.跳跃游戏II

122.买卖股票的最佳时机II 如果想到其实最终利润是可以分解的&#xff0c;那么本题就很容易了&#xff01; 如何分解呢&#xff1f; 假如第0天买入&#xff0c;第3天卖出&#xff0c;那么利润为&#xff1a;prices[3] - prices[0]。 相当于(prices[3] - prices[2]) (prices[2…

【华为OD机试】1043 - 从单向链表中删除指定值的节点

文章目录一、题目&#x1f538;题目描述&#x1f538;输入输出&#x1f538;样例1&#x1f538;样例2二、代码参考作者&#xff1a;KJ.JK&#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &am…
最新文章