开发者

Can @Autowired and default-autowire coexist?

开发者 https://www.devze.com 2023-02-14 10:37 出处:网络
<beans default-autowire=\"byType /> means that all fields of beans will automatically have dependencies injected if there is no more than 1 bean with the desired type.
<beans default-autowire="byType />

means that all fields of beans will automatically have dependencies injected if there is no more than 1 bean with the desired type.

The question is how does this work when annotations are used, and does it work at all.

My test showed that even if I use

@Resource(name="someConcreteFoo")
private Foo foo;

the context tries to autowire the field by type and fails if there are multiple implementations of Foo. So, for what I see, default-autowire doesn't mix with annotations. I couldn't find anything specific in the documentation.

To extend the question - how does spring behave with default-autowiring when xml-only is used. I.e. if you have <pr开发者_如何学编程operty>. Does the property injection override the default (it should be).

I can do more tests, but I'd prefer the behaviour being confirmed by some quotations. Any insights?


I had a quick stab at debugging this problem, and I think this may well be a bug in spring. In my opinion, the problem stems from the following code in AbstractAutowireCapableBeanFactory

/**
 * Populate the bean instance in the given BeanWrapper with the property values
 * from the bean definition.
 * @param beanName the name of the bean
 * @param mbd the bean definition for the bean
 * @param bw BeanWrapper with bean instance
 */
protected void populateBean(String beanName, AbstractBeanDefinition mbd, BeanWrapper bw) {
    PropertyValues pvs = mbd.getPropertyValues();

    if (bw == null) {
        if (!pvs.isEmpty()) {
            throw new BeanCreationException(
                    mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance");
        }
        else {
            // Skip property population phase for null instance.
            return;
        }
    }

    // Give any InstantiationAwareBeanPostProcessors the opportunity to modify the
    // state of the bean before properties are set. This can be used, for example,
    // to support styles of field injection.
    boolean continueWithPropertyPopulation = true;

    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof InstantiationAwareBeanPostProcessor) {
                InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
                if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
                    continueWithPropertyPopulation = false;
                    break;
                }
            }
        }
    }

    if (!continueWithPropertyPopulation) {
        return;
    }

    if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME ||
            mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) {
        MutablePropertyValues newPvs = new MutablePropertyValues(pvs);

        // Add property values based on autowire by name if applicable.
        if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME) {
            autowireByName(beanName, mbd, bw, newPvs);
        }

        // Add property values based on autowire by type if applicable.
        if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) {
            autowireByType(beanName, mbd, bw, newPvs);
        }

        pvs = newPvs;
    }

    boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();
    boolean needsDepCheck = (mbd.getDependencyCheck() != RootBeanDefinition.DEPENDENCY_CHECK_NONE);

    if (hasInstAwareBpps || needsDepCheck) {
        PropertyDescriptor[] filteredPds = filterPropertyDescriptorsForDependencyCheck(bw);
        if (hasInstAwareBpps) {
            for (BeanPostProcessor bp : getBeanPostProcessors()) {
                if (bp instanceof InstantiationAwareBeanPostProcessor) {
                    InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
                    pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
                    if (pvs == null) {
                        return;
                    }
                }
            }
        }
        if (needsDepCheck) {
            checkDependencies(beanName, mbd, filteredPds, pvs);
        }
    }

    applyPropertyValues(beanName, mbd, bw, pvs);
}

Personally I think the order of applying the autowiring and the InstantiationAwareBeanPostProcessor is wrong, as the @Resource annotation would only be applied in the postProcessPropertyValues, so after the autowiring (by which time the autowiring has failed already).

Now I don't know whether there would be an impact in changing the order of invocations, so that @Resource annotations are resolved before autowiring, but this may well be a something to raise as a bug/fix (I used the following way of loading my test application context to fix this issue):

    ApplicationContext ctx = new ClassPathXmlApplicationContext("test/appctx.xml") {
        protected org.springframework.beans.factory.support.DefaultListableBeanFactory createBeanFactory() {
            return new DefaultListableBeanFactory(getInternalParentBeanFactory()) {
                protected void populateBean(String beanName, org.springframework.beans.factory.support.AbstractBeanDefinition mbd, org.springframework.beans.BeanWrapper bw) {
                    PropertyValues pvs = mbd.getPropertyValues();

                    if (bw == null) {
                        if (!pvs.isEmpty()) {
                            throw new BeanCreationException(
                                    mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance");
                        }
                        else {
                            // Skip property population phase for null instance.
                            return;
                        }
                    }

                    // Give any InstantiationAwareBeanPostProcessors the opportunity to modify the
                    // state of the bean before properties are set. This can be used, for example,
                    // to support styles of field injection.
                    boolean continueWithPropertyPopulation = true;

                    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
                        for (BeanPostProcessor bp : getBeanPostProcessors()) {
                            if (bp instanceof InstantiationAwareBeanPostProcessor) {
                                InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
                                if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
                                    continueWithPropertyPopulation = false;
                                    break;
                                }
                            }
                        }
                    }

                    if (!continueWithPropertyPopulation) {
                        return;
                    }

                    boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();
                    boolean needsDepCheck = (mbd.getDependencyCheck() != RootBeanDefinition.DEPENDENCY_CHECK_NONE);

                    if (hasInstAwareBpps || needsDepCheck) {
                        PropertyDescriptor[] filteredPds = filterPropertyDescriptorsForDependencyCheck(bw);
                        if (hasInstAwareBpps) {
                            for (BeanPostProcessor bp : getBeanPostProcessors()) {
                                if (bp instanceof InstantiationAwareBeanPostProcessor) {
                                    InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
                                    pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
                                    if (pvs == null) {
                                        return;
                                    }
                                }
                            }
                        }
                        if (needsDepCheck) {
                            checkDependencies(beanName, mbd, filteredPds, pvs);
                        }
                    }

                    if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME ||
                            mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) {
                        MutablePropertyValues newPvs = new MutablePropertyValues(pvs);

                        // Add property values based on autowire by name if applicable.
                        if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME) {
                            autowireByName(beanName, mbd, bw, newPvs);
                        }

                        // Add property values based on autowire by type if applicable.
                        if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) {
                            autowireByType(beanName, mbd, bw, newPvs);
                        }

                        pvs = newPvs;
                    }

                    applyPropertyValues(beanName, mbd, bw, pvs);
                }
            };
        }
    };

Hope that helps


Edit:

Does the property injection override the default (it should be).

You are right. If you don't want Spring to inject dependency to a certain field of a bean then @Qualifier annotation can be used to inject desired dependency. I am still trying to find the documentation that would confirm it — the closest I could find is a post on spring forum override default-autowire setting with an annotation?

Edit: Here is another post @Resource considered only after default-autowire="byName" that describes using a new InstantiationAwareBeanPostProcessor to change wiring order to have @Resource on setter take precedence over default-autowire.


As far as I know the default-autowire attribute defines the default "autowire mode" for those beans wired in your XML configuration only! Then annotation based autowiring is independent from that. @Autowired is always by type, @Resource is always by name!
See the tip in Spring's reference documentation 3.9.3:
"If you intend to express annotation-driven injection by name, do not primarily use @Autowired, even if is technically capable of referring to a bean name through @Qualifier values. Instead, use the JSR-250 @Resource annotation, which is semantically defined to identify a specific target component by its unique name, with the declared type being irrelevant for the matching process."

0

精彩评论

暂无评论...
验证码 换一张
取 消