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

news2024/7/5 0:28:14

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就是当前类的clazz
				this.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)构建LifecycleElement
            LifecycleElement 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)构建LifecycleMetadata
         new 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);
    //判断是否需要重新解析clazz
    if (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);

    //构建InjectionMetadata
    return 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) {
            //无论是属性或方法都封装为ResourceElement
            element.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() {
        @Override
        public Class<?> getTargetClass() {
            return element.lookupType;
        }
        @Override
        public boolean isStatic() {
            return false;
        }
        @Override
        public Object getTarget() {
            //代理类的目标对象也是getResource方法获取的
            return getResource(element, requestingBeanName);
        }
        @Override
        public 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.coloradmin.cn/o/850978.html

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

相关文章

本地项目如何连接git远程仓库

在本地新建项目后&#xff0c;如何连接git远程仓库呢&#xff1f;步骤如下&#xff1a; 第一步&#xff0c; 首先我们在git上新建仓库&#xff0c;设置模板可勾选Readme文件。&#xff08;readme文件的创建是为了介绍所写代码的一些详细信息,为了之后更好的维护。&#xff09;…

记录问题: servlet获取项目包绝对路径

【2023-8-8 23:46:27 星期二】 如何获取在webapp下的路径?而不是target包下的webapp目录 比如这里应该获取到 F:\Tiam\Desktop\freemarker\freemarker-demo01\src\main\webapp 而readPath总是获取到 F:\Tiam\Desktop\freemarker\freemarker-demo01\target\freemarker-demo0…

Leetcode-每日一题【剑指 Offer 11. 旋转数组的最小数字】

题目 把一个数组最开始的若干个元素搬到数组的末尾&#xff0c;我们称之为数组的旋转。 给你一个可能存在 重复 元素值的数组 numbers &#xff0c;它原来是一个升序排列的数组&#xff0c;并按上述情形进行了一次旋转。请返回旋转数组的最小元素。例如&#xff0c;数组 [3,4…

Styletron: 面向组件的样式设计工具包

styletron官网&#xff1a; styletron的GitHub链接&#xff1a; styletron-react 一. 介绍 Styletron是一个通用的component-oriented&#xff08;面向组件的&#xff09;样式工具。它属于css-in-js类别。Styletron可以很好地与React配合使用&#xff0c;但也可以与其他框架或…

Allegro172版本无法低亮颜色的解决办法

Allegro172版本无法低亮颜色的解决办法 在用Allegro172版本做PCB设计的时候,高亮颜色是常用的命令,同时将高亮的颜色去高亮也是使用的十分频繁。 有时在去高亮的时候会出现无法去高亮的情况如下图 右边这块铜皮被高亮成了白色 可以看到即便使用去高亮命令,铜皮的颜色仍然还…

初次使用GPU云服务器

前言&#xff1a; 在体验了GPU云服务器&#xff08;GPU Cloud Computing&#xff0c;GPU&#xff09;后&#xff0c;我认为这是一个非常强大的弹性计算服务。它为深度学习、科学计算、图形可视化、视频处理等多种应用场景提供了强大的GPU算力&#xff0c;能够满足各类用户的计算…

web集群学习--基于CentOS构建LVS-DR集群、配置nginx负载均衡

基于CentOS构建LVS-DR集群 环境准备 主机名 ip地址 node1 192.168.1.140 client node2 192.168.1.141 LVS node3 192.168.1.142 RS1 node4 192.168.1.143 RS2配置 1.关闭防火墙和SELinux [rootclient~]# systemctl stop firewalld [rootclient~]# systemctl disabl…

实现UDP可靠性传输

文章目录 1、TCP协议介绍1.1、ARQ协议1.2、停等式1.3、回退n帧1.4、选择性重传 1、TCP协议介绍 TCP协议是基于IP协议&#xff0c;面向连接&#xff0c;可靠基于字节流的传输层协议 1、基于IP协议&#xff1a;TCP协议是基于IP协议之上传输的&#xff0c;TCP协议报文中的源端口IP…

【Linux升级之路】5_基础IO

&#x1f31f;hello&#xff0c;各位读者大大们你们好呀&#x1f31f; &#x1f36d;&#x1f36d;系列专栏&#xff1a;【Linux升级之路】 ✒️✒️本篇内容&#xff1a;文件操作&#xff0c;文件管理&#xff0c;重定向&#xff0c;简易shell添加重定向功能&#xff0c;文件属…

领航优配:暑期旅游市场热度持续攀升,相关公司业绩有望持续释放

到发稿&#xff0c;海看股份涨停&#xff0c;中广天择、探路者、众信旅行等涨幅居前。 8月8日&#xff0c;在线旅行板块震动上涨&#xff0c;到发稿&#xff0c;海看股份涨停&#xff0c;中广天择、探路者、众信旅行等涨幅居前。 今年以来&#xff0c;国内旅行商场逐渐恢复。文…

Unity制作护盾——2、力场冲击波护盾

Unity制作力场护盾 大家好&#xff0c;我是阿赵。   继续做护盾&#xff0c;这一期做一个力场冲击波护盾。 一、效果展示 主要的效果并不是这个球&#xff0c;而是护盾在被攻击的时候&#xff0c;会出现一个扩散的冲击波。比如上图在右边出现了冲击波 如果在左边被攻击&am…

安装ubuntu 18.04 系统(1)——制作系统安装U盘

https://rufus.ie/zh/ 下载该软件&#xff0c;准备制作启动盘下载自己想要的镜像&#xff0c;http://mirrors.163.com/ubuntu-releases/18.04/&#xff0c; 我选择的是ubuntu-18.04.6-live-server-amd64.iso 因为&#xff0c;科研写程序使用&#xff0c;不需要桌面版本。开始制…

分布式协议与算法——拜占庭将军问题

拜占庭将军问题 背景&#xff1a;以战国时期为背景 战国时期&#xff0c;齐、楚、燕、韩、赵、魏、秦七雄并立&#xff0c;后来秦国的势力不断强大起来&#xff0c;成了东方六国的共同威胁。于是&#xff0c;这六个国家决定联合&#xff0c;全力抗秦&#xff0c;免得被秦国各个…

go错误集(持续更新)

1.提示以下报错 Build Error: go build -o c:\Users\Administrator\Desktop__debug_bin2343731882.exe -gcflags all-N -l . go: go.mod file not found in current directory or any parent directory; see ‘go help modules’ (exit status 1) 解决办法&#xff1a; go …

mysql进阶篇(二)

前言 「作者主页」&#xff1a;雪碧有白泡泡 「个人网站」&#xff1a;雪碧的个人网站 「推荐专栏」&#xff1a; ★java一站式服务 ★ ★ React从入门到精通★ ★前端炫酷代码分享 ★ ★ 从0到英雄&#xff0c;vue成神之路★ ★ uniapp-从构建到提升★ ★ 从0到英雄&#xff…

知识贴:如何使用校对软件改善新闻稿件的质量

使用校对软件改善新闻稿件的质量可以按照以下步骤进行&#xff1a; 1.选择适合的校对软件&#xff1a;市场上有许多校对软件可供选择&#xff0c;例如语法和拼写检查工具&#xff0c;自动校对工具以及专门为新闻写作而设计的工具。根据自己的需求和预算&#xff0c;选择最适合的…

山西电力市场日前价格预测【2023-08-09】

日前价格预测 预测明日&#xff08;2023-08-09&#xff09;山西电力市场全天平均日前电价为335.82元/MWh。其中&#xff0c;最高日前电价为364.48元/MWh&#xff0c;预计出现在19: 45。最低日前电价为306.83元/MWh&#xff0c;预计出现在13: 30。 价差方向预测 1&#xff1a; 实…

C语言__LINE__和#line学习

这是ANSI C 预定义的&#xff0c; __LINE__ &#xff0c;包含当前行号&#xff0c;一个十进制常量&#xff1b; #line指令用于改变 __LINE__的内容&#xff1b; 先看一下VC6控制台程序&#xff0c; printf这行是第五行&#xff0c;输出了 5 &#xff1b; 使用#line重新定义了…

Java源码解析-重点集合框架篇

Java 源码解析&#xff0c;集合篇 一&#xff1a;故事背景二&#xff1a;数据结构2.1 线性结构2.2 非线性结构 三&#xff1a;集合分类3.1 结构图 四&#xff1a;详细分析4.1 List4.1.1 ArrayList4.1.1.1 底层结构4.1.1.2 主要特点 4.1.2 LinkedList4.1.2.1 底层结构4.1.2.2 主…

SAM 大模型Colab快速上手【Segment Anything Model】

Google Colab 是一个基于云的 Jupyter 笔记本环境&#xff0c;允许您通过浏览器编写、运行和共享 Python 代码。 它就像 Google 文档&#xff0c;但用于代码。 通过免费版本的 Google Colab&#xff0c;你可以获得带有约 16GPU VRAM 的 Nvidia Tesla T4 GPU&#xff0c;这对于…