@Resource与@Autowired代码实现的区别以及,@PostConstruct,@PreDestroy标签的解析CommonAnnotationBeanPostProcessor

1.@Resource@Autowired

@Resource标签在日常的Spring开发中跟@Autowired标签一样经常被用来注入对象用。虽然两者的作用一样,但是还是有区别的。这里简单说一下在注入时候区别

1.使用上的区别
1.@Resource

@Resource装配顺序

  1. 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
  2. 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
  3. 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常
  4. 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配;
2.@Autowired

@Autowired默认按类型装配(这个注解是属业spring的),默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false

2.实现上的区别

关于@Autowired的实现前面已经分析过Spring源码-----AutowiredAnnotationBeanPostProcessor解析Autowired@Resource的实现则是在CommonAnnotationBeanPostProcessor类中实现的。

2.1 @Resource 的实现

 与@Autowired的实现类似 @Resource过程也是类似的,这里在基于前面分析@Autowired的基础上进行讲解。前面说过AnnotationConfigUtils类的registerAnnotationConfigProcessors方法的作用以及调用时机,在这个方法中不仅注册了与AutowiredAnnotationBeanPostProcessor有关的Bean还注册了CommonAnnotationBeanPostProcessor相关的Bean,后面也说了这些Bean的实例化以及初始化的时机。在CommonAnnotationBeanPostProcessor中间接也实现了MergedBeanDefinitionPostProcessor类的postProcessMergedBeanDefinition方法,在这个方法中进行第一次@Resource标签的解析以及相关数据的保存,逻辑跟@Autowired的一样。

	public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
		super.postProcessMergedBeanDefinition(beanDefinition, beanType, beanName);
		InjectionMetadata metadata = findResourceMetadata(beanName, beanType, null);
		metadata.checkConfigMembers(beanDefinition);
	}

 真正的进行注入的逻辑是在Bean 的生命周期中的postProcessProperties方法调用的时候完成的。关于生命周期前面用流程图的方式详细分析过Spring的Bean生命周期流程图及代码解释。这里不在描述。在这个方法中,会再次获取一次当前初始化的bean中的贴有 @Resource标签的Bean,并保存在InjectionMetadata对象中,然后进行注入。

	public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
		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;
	}
2.2注入细节上的实现(两个标签注入方式不同的原因)

 这两个标签最后都会将需要注入的信息保存在InjectionMetadata对象中,但是最后注入的逻辑却不一样。

2.2.1 @Resource注入实现

@Resource的注入方法最后由自己实现的,先看InjectionMetadata类的inject方法

		protected void inject(Object target, @Nullable String requestingBeanName, @Nullable PropertyValues pvs)
				throws Throwable {
			//如果是字段
			if (this.isField) {
				Field field = (Field) this.member;
				ReflectionUtils.makeAccessible(field);
				//反射调用注入 --getResourceToInject--
				field.set(target, getResourceToInject(target, requestingBeanName));
			}
			//不是字段则是方法
			else {
				//检查是否是需要跳过的属性
				if (checkPropertySkipping(pvs)) {
					return;
				}
				try {
					//获取注入的set方法
					Method method = (Method) this.member;
					ReflectionUtils.makeAccessible(method);
					//反射调用注入  --getResourceToInject--
					method.invoke(target, getResourceToInject(target, requestingBeanName));
				}
				catch (InvocationTargetException ex) {
					throw ex.getTargetException();
				}
			}
		}

 这里主要是判断注入是在字段还是在方法上面,其中在使用反射的方式注入的时候,有一个getResourceToInject方法。这个方法的作用是从上下文获取注入的对象(如果将A注入到a字段,那么这个方法就是从上下文获取A)。这个方法在InjectionMetadata是空实现的。在CommonAnnotationBeanPostProcessor实现了自己的获取对象的逻辑。现在CommonAnnotationBeanPostProcessor类的内部类ResourceElement中查看实现的方法逻辑

		protected Object getResourceToInject(Object target, @Nullable String requestingBeanName) {
			//检查当前字段需要注入的对象是否延迟初始化,是的创建一个延迟初始化的代理源对象,如果不是则查找需要注入的对象
			return (this.lazyLookup ? buildLazyResourceProxy(this, requestingBeanName) :
					getResource(this, requestingBeanName));
		}

 这里进入其中的getResource方法,因为在buildLazyResourceProxy方法中也会调用这个方法来获取需要被代理的对象的。

	protected Object getResource(LookupElement element, @Nullable String requestingBeanName)
			throws NoSuchBeanDefinitionException {
		//mappedName是否不为空,这里的mappedName,如果@Resource标签中的lookup属性不是空这为lookup属性,如果lookup是空的
		//则进一步获取@Resource中的mappedName属性
		//lookupType相当于type属性
		if (StringUtils.hasLength(element.mappedName)) {
			//使用SimpleJndiBeanFactory获取Bean
			return this.jndiFactory.getBean(element.mappedName, element.lookupType);
		}
		//是否使用JNDI的方式查找Bean,java5的方式,默认是false
		if (this.alwaysUseJndiLookup) {
			//使用SimpleJndiBeanFactory获取Bean
			return this.jndiFactory.getBean(element.name, element.lookupType);
		}
		//如果resourceFactory不是null,resourceFactory一般不会是null,除非人为设置
		if (this.resourceFactory == null) {
			throw new NoSuchBeanDefinitionException(element.lookupType,
					"No resource factory configured - specify the 'resourceFactory' property");
		}
		//如果没有指定name也没有指定type则从spring的BeanFactory中按照requestingBeanName获取Bean,,也就是根据beanName获取
		return autowireResource(this.resourceFactory, element, requestingBeanName);
	}

 从上面的分析可以看出来 @Resource的注入原理,是现根据指定的name或者type来用JNDI的方式注入,如果都没有指定则使用spring的注入的方式来根据字段的名字来注入

2.2.2@Autowired注入实现

@Resource不一样@Autowired的注入是在AutowiredAnnotationBeanPostProcessor类中实现了InjectionMetadata类的inject方法。

		protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
			Field field = (Field) this.member;
			Object value;
			//是否已经存在缓存需要注入的对象和被注入对象的依赖关系,是的则获取缓存的对象
			if (this.cached) {
				value = resolvedCachedArgument(beanName, this.cachedFieldValue);
			}
			else {
				//创建依赖关系描述对象
				DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
				desc.setContainingClass(bean.getClass());
				Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
				Assert.state(beanFactory != null, "No BeanFactory available");
				TypeConverter typeConverter = beanFactory.getTypeConverter();
				try {
					//从beanFactory中获取需要注入的对象
					value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
				}
				catch (BeansException ex) {
					throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
				}
				synchronized (this) {
					if (!this.cached) {
						if (value != null || this.required) {
							this.cachedFieldValue = desc;
							//将要被注入的Bean注册要当前要注入对象的依赖Bean
							registerDependentBeans(beanName, autowiredBeanNames);
							if (autowiredBeanNames.size() == 1) {
								String autowiredBeanName = autowiredBeanNames.iterator().next();
								if (beanFactory.containsBean(autowiredBeanName) &&
										beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
									this.cachedFieldValue = new ShortcutDependencyDescriptor(
											desc, autowiredBeanName, field.getType());
								}
							}
						}
						else {
							this.cachedFieldValue = null;
						}
						this.cached = true;
					}
				}
			}
			if (value != null) {
				ReflectionUtils.makeAccessible(field);
				//设置值
				field.set(bean, value);
			}
		}
	}

 可以看到@Autowired的注入逻辑是先看是否注入过,如果是的则获取已经存在的依赖关系,如果还不存在则创建一个依赖关系对象,并根据beanName去spring中寻找需要注入的对象然后设置到依赖关系中并保存起来。最后将寻找到的bean设置到字段中。

2.@PostConstruct@PreDestroy

2.1@PostConstruct@PreDestroy的获取

@PostConstruct@PreDestroy的实现是在CommonAnnotationBeanPostProcessor的父类InitDestroyAnnotationBeanPostProcessor中实现的。
 两个注解的的实现跟前面两个注入对象的标签的实现也是有点类似的,在postProcessMergedBeanDefinition方法中会先寻找一次带有@PostConstruct@PreDestroy注解方法,分别保存到由LifecycleElement组成的两个集合中,一个是初始化方法集合initMethods一个是销毁方法集合destroyMethods

	private LifecycleMetadata buildLifecycleMetadata(final Class<?> clazz) {
		//检查是否存在初始化方法标签集合initAnnotationType或者销毁方法标签集合destroyAnnotationType中的标签,不存在则返回空的LifecycleMetadata对象
		if (!AnnotationUtils.isCandidateClass(clazz, Arrays.asList(this.initAnnotationType, this.destroyAnnotationType))) {
			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, method -> {
				//查找贴有初始化方法标签集合initAnnotationType中标签的方法
				if (this.initAnnotationType != null && method.isAnnotationPresent(this.initAnnotationType)) {
					LifecycleElement element = new LifecycleElement(method);
					currInitMethods.add(element);
					if (logger.isTraceEnabled()) {
						logger.trace("Found init method on class [" + clazz.getName() + "]: " + method);
					}
				}
				//查找贴有销毁方法标签集合destroyAnnotationType中的标签的方法
				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);
		//如果初始化方法跟销毁都是空则返回空的LifecycleMetadata,否则返回包含他们的LifecycleMetadata
		return (initMethods.isEmpty() && destroyMethods.isEmpty() ? this.emptyLifecycleMetadata :
				new LifecycleMetadata(clazz, initMethods, destroyMethods));
	}
2.2@PostConstruct@PreDestroy方法的调用

 前面已经分析了贴有注解方法的获取,剩下的就是调用了。

2.2.1贴有@PostConstruct方法的调用

@PostConstruct解析后,调用贴有这个标签的方法的位置在Bean的生命周期中的postProcessBeforeInitialization方法,在InitDestroyAnnotationBeanPostProcessor中。

	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		//获取生命周期方法,转换为LifecycleMetadata对象
		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;
	}
2.2.1贴有@PreDestroy方法的调用

 逻辑上跟@PostConstruct一样,只不过是调用的位置不一样,在Bean的生命周期中的postProcessBeforeDestruction中调用

	@Override
	public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
		//获取生命周期方法,转换为LifecycleMetadata对象
		LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass());
		try {
			//调用对象中的方法
			metadata.invokeDestroyMethods(bean, beanName);
		}
		catch (InvocationTargetException ex) {
			String msg = "Destroy method on bean with name '" + beanName + "' threw an exception";
			if (logger.isDebugEnabled()) {
				logger.warn(msg, ex.getTargetException());
			}
			else {
				logger.warn(msg + ": " + ex.getTargetException());
			}
		}
		catch (Throwable ex) {
			logger.warn("Failed to invoke destroy method on bean with name '" + beanName + "'", ex);
		}
	}

 到这里就解析完毕了

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值