`
jinnianshilongnian
  • 浏览: 21434828 次
  • 性别: Icon_minigender_1
博客专栏
5c8dac6a-21dc-3466-8abb-057664ab39c7
跟我学spring3
浏览量:2405130
D659df3e-4ad7-3b12-8b9a-1e94abd75ac3
Spring杂谈
浏览量:2997788
43989fe4-8b6b-3109-aaec-379d27dd4090
跟开涛学SpringMVC...
浏览量:5631528
1df97887-a9e1-3328-b6da-091f51f886a1
Servlet3.1规范翻...
浏览量:257583
4f347843-a078-36c1-977f-797c7fc123fc
springmvc杂谈
浏览量:1593212
22722232-95c1-34f2-b8e1-d059493d3d98
hibernate杂谈
浏览量:248982
45b32b6f-7468-3077-be40-00a5853c9a48
跟我学Shiro
浏览量:5847622
Group-logo
跟我学Nginx+Lua开...
浏览量:698184
5041f67a-12b2-30ba-814d-b55f466529d5
亿级流量网站架构核心技术
浏览量:780495
社区版块
存档分类
最新评论

注入FactoryBean失败分析+解决方案

阅读更多

如果有朋友 遇到Spring疑难杂症,小弟愿意帮忙分析及提出解决方案。

 

通过spring注入FactoryBean时可能会遇到找不到依赖的异常“Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException:”,但是很多朋友会说明明我配置了,怎么找不到呢?或者如果直接从Spring容器去getBean是能拿到的,为什么注入不行呢?所以此处给大家分析一下原因,希望解决一些朋友的的疑惑。

 

假设: 

A {
    @Autowired B b;
} 

B implements FactoryBean {

}

假设我们有两个类:

1、都是单例Bean;

2、A依赖于B; B是一个FactoryBean;

3、A先于B加载,否则就没有问题了。

 

分析:

1、容器启动时默认会预初始化单例Bean,初始化顺序是无序的,因为在Spring容器内部使用Map存储Bean定义;当然也可以开启如lazy-init,不过还是无序。

 

1.1、比如DefaultListableBeanFactory,使用preInstantiateSingletons方法进行预初始化单例Bean;如果是ClasspathXmlApplicationContext会在其如refresh时调用此方法进行预初始化单例Bean;

1.2、如果是FactoryBean,并且(非SmartFactoryBean且eagerInit=false),那么默认只实例化FactoryBean,不会调用getObject去获取其具体的Bean;如下所示 

			if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
				if (isFactoryBean(beanName)) {
					final FactoryBean<?> factory = (FactoryBean<?>) getBean(FACTORY_BEAN_PREFIX + beanName);
					boolean isEagerInit;
					if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
						isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
							public Boolean run() {
								return ((SmartFactoryBean<?>) factory).isEagerInit();
							}
						}, getAccessControlContext());
					}
					else {
						isEagerInit = (factory instanceof SmartFactoryBean &&
								((SmartFactoryBean<?>) factory).isEagerInit());
					}
					if (isEagerInit) {
						getBean(beanName);
					}
				}
				else {
					getBean(beanName);
				}
			}

如果是SmartFactoryBean且是EagerInit(true),那么调用getBean得到FactoryBean对应的具体Bean,即调用FactoryBean.getObject获取; 否则只实例化FactoryBean,不会返回具体的Bean; 此处需要注意的是:FactoryBean会完成实例化、依赖注入、初始化整个逻辑,而不是后边咱们提到的只调用部分逻辑。

 

2、当容器实例化A后,开始注入B;因为我们通过@Autowired注入B;所以Spring使用的是AutowiredAnnotationBeanPostProcessor注入:

具体可参考我之前写的《Spring开闭原则的表现-BeanPostProcessor扩展点》。

 

3、AutowiredAnnotationBeanPostProcessor使用内部的AutowiredFieldElement进行注入,具体调用了beanFactory的如下代码: 

value = beanFactory.resolveDependency(descriptor, beanName, autowiredBeanNames, typeConverter);

 

4、 在我们的场景中会使用resolveDependency中的如下代码(部分)去查找候选Bean: 

			Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
			if (matchingBeans.isEmpty()) {
				if (descriptor.isRequired()) {
					raiseNoSuchBeanDefinitionException(type, "", descriptor);
				}
				return null;
			}
			if (matchingBeans.size() > 1) {
				String primaryBeanName = determinePrimaryCandidate(matchingBeans, descriptor);
				if (primaryBeanName == null) {
					throw new NoUniqueBeanDefinitionException(type, matchingBeans.keySet());
				}
				if (autowiredBeanNames != null) {
					autowiredBeanNames.add(primaryBeanName);
				}
				return matchingBeans.get(primaryBeanName);
			}
			// We have exactly one match.
			Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();
			if (autowiredBeanNames != null) {
				autowiredBeanNames.add(entry.getKey());
			}
			return entry.getValue();
		}

大家可以看到,使用findAutowireCandidates去发现候选Bean:

 

1、如果没有找到,抛出之前说的没有找到Bean异常;

2、如果发现多个,但是需要一个,抛出发现多于一个Bean的异常;

3、否则注入一个。

 

5、此时需要去分析findAutowireCandidates方法,在此方法内其使用如下代码去查找候选Bean的名字,而且递归查找父BeanFactory中的: 

String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
				this, requiredType, true, descriptor.isEager());

6、findAutowireCandidates委托给如下代码去查找: 

String[] result = lbf.getBeanNamesForType(type, includeNonSingletons, allowEagerInit);

7、接着委托给如下代码 接着去查找匹配的Bean名字: 

				try {
					RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
					// Only check bean definition if it is complete.
					if (!mbd.isAbstract() && (allowEagerInit ||
							((mbd.hasBeanClass() || !mbd.isLazyInit() || this.allowEagerClassLoading)) &&
									!requiresEagerInitForType(mbd.getFactoryBeanName()))) {
						// In case of FactoryBean, match object created by FactoryBean.
						boolean isFactoryBean = isFactoryBean(beanName, mbd);
						boolean matchFound = (allowEagerInit || !isFactoryBean || containsSingleton(beanName)) &&
								(includeNonSingletons || isSingleton(beanName)) && isTypeMatch(beanName, type);
						if (!matchFound && isFactoryBean) {
							// In case of FactoryBean, try to match FactoryBean instance itself next.
							beanName = FACTORY_BEAN_PREFIX + beanName;
							matchFound = (includeNonSingletons || mbd.isSingleton()) && isTypeMatch(beanName, type);
						}
						if (matchFound) {
							result.add(beanName);
						}
					}
				}

因为我们的B是一个FactoryBean,而且B还未实例化,所以走: 

						if (!matchFound && isFactoryBean) {
							// In case of FactoryBean, try to match FactoryBean instance itself next.
							beanName = FACTORY_BEAN_PREFIX + beanName;
							matchFound = (includeNonSingletons || mbd.isSingleton()) && isTypeMatch(beanName, type);
						}

8、此时具体要看isTypeMatch方法了:代码比较多,此时我只贴将执行的代码片段: 

			// Check bean class whether we're dealing with a FactoryBean.
			if (FactoryBean.class.isAssignableFrom(beanClass)) {
				if (!BeanFactoryUtils.isFactoryDereference(name)) {
					// If it's a FactoryBean, we want to look at what it creates, not the factory class.
					Class<?> type = getTypeForFactoryBean(beanName, mbd);
					return (type != null && typeToMatch.isAssignableFrom(type));
				}
				else {
					return typeToMatch.isAssignableFrom(beanClass);
				}
			}

此处会调用Class<?> type = getTypeForFactoryBean(beanName, mbd); 去获取FactoryBean的类型。getTypeForFactoryBean方法的核心代码如下所示: 

		FactoryBean<?> fb = (mbd.isSingleton() ?
				getSingletonFactoryBeanForTypeCheck(beanName, mbd) :
				getNonSingletonFactoryBeanForTypeCheck(beanName, mbd));

		if (fb != null) {
			// Try to obtain the FactoryBean's object type from this early stage of the instance.
			objectType.value = getTypeForFactoryBean(fb);
			if (objectType.value != null) {
				return objectType.value;
			}
		}

		// No type found - fall back to full creation of the FactoryBean instance.
		return super.getTypeForFactoryBean(beanName, mbd);

 

9、如果是单例Bean,执行getSingletonFactoryBeanForTypeCheck(beanName, mbd)获取FactoryBean: 

	private FactoryBean getSingletonFactoryBeanForTypeCheck(String beanName, RootBeanDefinition mbd) {
		synchronized (getSingletonMutex()) {
			BeanWrapper bw = this.factoryBeanInstanceCache.get(beanName);
			if (bw != null) {
				return (FactoryBean) bw.getWrappedInstance();
			}
			if (isSingletonCurrentlyInCreation(beanName)) {
				return null;
			}
			Object instance = null;
			try {
				// Mark this bean as currently in creation, even if just partially.
				beforeSingletonCreation(beanName);
				// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
				instance = resolveBeforeInstantiation(beanName, mbd);
				if (instance == null) {
					bw = createBeanInstance(beanName, mbd, null);
					instance = bw.getWrappedInstance();
				}
			}
			finally {
				// Finished partial creation of this bean.
				afterSingletonCreation(beanName);
			}
			FactoryBean fb = getFactoryBean(beanName, instance);
			if (bw != null) {
				this.factoryBeanInstanceCache.put(beanName, bw);
			}
			return fb;
		}
	}

从如上代码可以看到的是,只执行实例化,没有执行依赖注入和初始化。  

 

10、接着调用objectType.value = getTypeForFactoryBean(fb);获取FactoryBean包装的具体类型: 

return factoryBean.getObjectType();

 即通过getObjectType获取具体的类型。

  

结论

如上是整个注入FactoryBean的代码分析,即FactoryBean在实例化/依赖注入时做了特殊处理,所以会造成问题:

1、在容器初始化时,如果FactoryBean是单例Bean,默认只实例化、依赖注入和初始化FactoryBean,不会自动调用getObject返回具体Bean;

2、如果A依赖的FactoryBean B还没有创建,那么执行依赖注入时:只执行FactoryBean B的实例化,不执行执行依赖注入和初始化。 

 

---------------------------------------------------分割线------------------------------------------------ 

接着看一下ProxyFactoryBean可能会遇到的问题。

 

问题1:ProxyBean

请参考《Spring 3.2.2 AOP引入方式集成测试的问题》 

    <context:component-scan base-package="com.myapp.aop.introduce" />
    <bean id="forumService" class="org.springframework.aop.framework.ProxyFactoryBean"
        p:interfaces="com.myapp.aop.introduce.Monitorable" p:target-ref="forumServiceTarget"
        p:interceptorNames="pmonitor" p:proxyTargetClass="true" />
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "/aop/introduce/applicationContext.xml" })
public class ForumServiceTest{

    @Autowired
    @Qualifier(value = "forumService")
    ForumService forumService;

    @Test
    public void test() {
        forumService.removeForum(10);
        forumService.removeTopic(1022);
        Monitorable moniterable = (Monitorable)forumService;
        moniterable.setMonitorActive(true);
        forumService.removeForum(10);
        forumService.removeTopic(1022); 
    }
}

抛出org.springframework.beans.factory.NoSuchBeanDefinitionException的异常,指向forumService不存在

 

分析:  

1、首先此处spring容器会加载配置文件并先完成初始化,此时会实例化、依赖注入及初始化forumService这个ProxyFactoryBean(即是FactoryBean);即符合之前提到的结论中的【1】;所以此时forumService这个FactoryBean已经初始化完成了;但因为没有调用getObject 所以还未实例化具体的Bean;

2、当进行ForumServiceTest的依赖注入时(ForumService forumService;),此时会按照如上提到的步骤执行,当执行到第【9】步时,因为之前已经完成了ProxyFactoryBean的初始化,所以此时直接返回factoryBeanInstanceCache中的FactoryBean;

3、接着会调用其getObjectType得到具体Bean的类型: 

	public Class<?> getObjectType() {
		synchronized (this) {
			if (this.singletonInstance != null) {
				return this.singletonInstance.getClass();
			}
		}
		Class[] ifcs = getProxiedInterfaces();
		if (ifcs.length == 1) {
			return ifcs[0];
		}
		else if (ifcs.length > 1) {
			return createCompositeInterface(ifcs);
		}
		else if (this.targetName != null && this.beanFactory != null) {
			return this.beanFactory.getType(this.targetName);
		}
		else {
			return getTargetClass();
		}
	}

3.1、首先判断singletonInstance是否已经创建了,此时因为没有调用getObject,所以还是null;

 

3.2、因此到了此步骤,getProxiedInterfaces会得到之前在配置文件中注入的“com.myapp.aop.introduce.Monitorable”,因此会返回这个;

 

所以spring容器断定你的Bean类型是“com.myapp.aop.introduce.Monitorable”,因此和你的ForumService不兼容,因此赋值是失败的。

 

解决方案:

1、使用depends-on,如<bean class="a" depends-on="forumService"/>,这样会在实例化a时,先实例化forumService,因为获取依赖时使用getBean(dependsOnBean);,即走的是完成流程,也不会有问题;

2、注入ApplicationContext,然后手工ctx.getBean("forumService", ForumService.class);  这样会造成FactoryBean调用getObject返回具体Bean,即ForumService的代理,也是没问题的;

3、再写一个ProxyFactoryBean,实现SmartFactoryBean,且getEarlyInit()方法返回true也是可以解决这个问题的,不过比较麻烦,直接使用1/2即可。

 

---------------------------------------------------分割线------------------------------------------------ 

接下来分析下 spring data jpa+shiro Realm时的问题吧,这个估计用过的都会遇到这个问题。

比如我写的:UserRealm依赖UserServiceUserService依赖UserRepository接口。

 

1、此处假设先实例化UserRealm,此时会去查找并实例化UserService依赖,然后UserService接着去查找UserRepository依赖;

2、UserRealm查找依赖UserService没有问题,但是UserService查找UserRepository会有问题;

 

分析:

1、假设spring data jpa配置是spring-config.xml: 

    <jpa:repositories
            base-package="com.sishuok.es.**.repository"
            entity-manager-factory-ref="entityManagerFactory"
            transaction-manager-ref="transactionManager">
    </jpa:repositories>

那么spring data jpa 会在容器启动时使用org.springframework.data.jpa.repository.config.JpaRepositoryNameSpaceHandler去扫描com.sishuok.es.**.repository包下的所有继承org.springframework.data.repository.Repository的接口;这个大家可以去看下org.springframework.data.jpa.repository.config.JpaRepositoryNameSpaceHandler实现;

 

 

2、扫描后默认使用org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean,当然也可以在jpa:repositories中使用factory-class="com.sishuok.es.common.repository.support.SimpleBaseRepositoryFactoryBean" 注册自己的FactoryBean,

 

3、其内部getObjectType为:

	public Class<? extends T> getObjectType() {
		return (Class<? extends T>) (null == repositoryInterface ? Repository.class : repositoryInterface);
	}

 但是此时repositoryInterface并没有注入,参见第【9】步,所以返回的是Repository.class;

 

4、因此会注入失败。

 

 

解决方案和第一个一样:

1、使用depends-on,如<bean class="userRealm" depends-on="userRepository"/>,这样会在实例化userRealm时,先实例化userRepository,因为获取依赖时使用getBean(dependsOnBean);,即走的是完成流程,也不会有问题;可以参见UserService的历史版本;(在UserRealm或UserService上都行,这个没有影响)

2、注入ApplicationContext,然后手工ctx.getBean("userRepository", UserRepository.class);  这样会造成FactoryBean调用getObject返回具体Bean,也是没问题的;

 

因为我的UserRealm可能依赖的比较多,所以直接:ctx.getBeansOfType(SimpleBaseRepositoryFactoryBean.class); 可以解决依赖多个的问题,不需要每次加新的后加depends-on。

 

 

---------------------------------------------------分割线------------------------------------------------ 

 

所以总的结论是:

如上是整个注入FactoryBean的代码分析,即FactoryBean在实例化/依赖注入时做了特殊处理,所以会造成问题:

1、在容器初始化时,如果FactoryBean是单例Bean,默认只实例化、依赖注入和初始化FactoryBean,不会自动调用getObject返回具体Bean;

2、如果A依赖的FactoryBean B还没有创建,那么执行依赖注入时:只执行FactoryBean B的实例化,不执行执行依赖注入和初始化。 

 

总的解决方案是:

  1. 使用depends-on,如<bean class="userRealm" depends-on="userRepository"/>,这样会在实例化userRealm时,先实例化userRepository,因为获取依赖时使用getBean(dependsOnBean);,即走的是完成流程,也不会有问题;可以参见UserService的历史版本;(在UserRealm或UserService上都行,这个没有影响)
  2. 注入ApplicationContext,然后手工ctx.getBean("userRepository", UserRepository.class);  这样会造成FactoryBean调用getObject返回具体Bean,也是没问题的;

 

 

7
2
分享到:
评论
18 楼 whm9276 2017-11-08  
大神,麻烦帮我看下我的问题,你会感兴趣的
http://ask.csdn.net/questions/656781
17 楼 andyadc 2015-08-11  
请问mybatis作为持久层与shiro整合时出现这种情况,如何解决呢,
Bean 'sqlSessionFactory' of type [class org.mybatis.spring.SqlSessionFactoryBean] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying),
16 楼 xing_kenny 2015-06-02  
我这里发现一个问题

@Service
@Transactional
public class Client {

@Autowired
private UserRepository ur;

public static void main(String[] args) {

ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Client c = (Client)ctx.getBean("client");

       
        }

对于上面的代码

我使用 java -Djava.ext.dirs=libs  Client 可以执行,

把项目打成 runnable jar 用 java -jar holleword.jar 执行,则报

Exception in thread "main" java.lang.reflect.InvocationTargetException
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:606)
        at org.eclipse.jdt.internal.jarinjarloader.JarRsrcLoader.main(JarRsrcLoader.java:58)
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'client' is defined

请教什么原因?
15 楼 xing_kenny 2015-06-02  
我这里发现一个问题

@Service
@Transactional
public class Client {

@Autowired
private UserRepository ur;

public static void main(String[] args) {

ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Client c = (Client)ctx.getBean("client");

       
        }

对于上面的代码

我使用 java -Djava.ext.dirs=libs  Client 可以执行,
14 楼 wuwenjie0506 2015-01-21  
其实BeanFactory的getObjectType注释已经写的很清楚:
This method can be called before this FactoryBean has been fully initialized. It must not rely on state created during initialization; of course, it can still use such state if available.
返回的类型最好不要依赖实例化后的注入属性状态,这样可能会导致通过返回的type不正确。
AbstractAutowireCapableBeanFactory调用getTypeForFactoryBean过程中为了优化,而调用了getSingletonFactoryBeanForTypeCheck,居然创建没有实例化完成的FactoryBean! 但是别人的注释已写清楚,也没得办法提bug,优化其实得不尝试。如果索性不知道的情况下返回null,反到是会调用super.getTypeForFactoryBean,问题就没了。
所以还是需要FactoryBean写正确,也就是在如果依赖属性不存在就返回null.正所谓,不知为不知,是知也. Spring技巧用的太多,不过还是善意的提醒过了。
P.S. 给出的解决方案不不优雅,直接使用@Resource注入就成功绕开。
13 楼 sislover 2014-08-07  
确实,有时候就是不灵,非要depend-on,同样的代码,在某一台机器可以,其他的就不行。spring有时候也有点坑人的说。。。。
12 楼 xunke515 2014-05-21  
感谢开涛老师得精彩分析~ 问题解决了~ 顶个
11 楼 nidonglin1986 2013-11-04  
大牛啊,今天晚上加班搞到10点多就是因为这个注入注入问题。。。有时间好好看看你写的书。非常感谢。
10 楼 jinnianshilongnian 2013-06-27  
kjmmlzq19851226 写道
@Autowired
jinnianshilongnian 写道
而且最好是那种疑难杂症

我现在都没有搞懂spring注解是怎么工作的,spring是怎么通过@去di的?


你可以参考我之前写的《Spring开闭原则的表现-BeanPostProcessor的扩展点》
http://jinnianshilongnian.iteye.com/blog/1489787
http://jinnianshilongnian.iteye.com/blog/1492424

是通过BeanPostProcessor
9 楼 kjmmlzq19851226 2013-06-27  
@Autowired
jinnianshilongnian 写道
而且最好是那种疑难杂症

我现在都没有搞懂spring注解是怎么工作的,spring是怎么通过@去di的?
8 楼 jinnianshilongnian 2013-06-27  
而且最好是那种疑难杂症
7 楼 jinnianshilongnian 2013-06-27  
kjmmlzq19851226 写道
jinnianshilongnian 写道
sgq0085 写道
非常感谢!

有任何SSH的疑难杂症找我  

必须的

我非常希望你们有问题。哈哈 
6 楼 kjmmlzq19851226 2013-06-27  
jinnianshilongnian 写道
sgq0085 写道
非常感谢!

有任何SSH的疑难杂症找我  

必须的
5 楼 jinnianshilongnian 2013-06-27  
sgq0085 写道
非常感谢!

有任何SSH的疑难杂症找我  
4 楼 sgq0085 2013-06-27  
非常感谢!
3 楼 kjmmlzq19851226 2013-06-27  
2 楼 jinnianshilongnian 2013-06-27  
如果有朋友 遇到Spring疑难杂症,小弟愿意帮忙分析及提出解决方案。
1 楼 jinnianshilongnian 2013-06-27  
问题本身的原因主要是:FactoryBean和普通Bean使用了不一样的策略造成的,Spring认为这是优化,这样在检查类型时可以快速完成

但是缺点也很明显,如果你注入FactoryBean,你不仅仅是进行类型检查,所以很可能造成失败,得不偿失。

相关推荐

Global site tag (gtag.js) - Google Analytics