起因
由于项目需要把一个项目的某个功能移植到另一个项目中,但是两个项目结构不同,新写的接口不是被Spring容器管理的,没办法在接口中使用@Autowired注解自动注入Serivice(Ps.我这里说的接口是对外接口,不是Java中的interface,实际上是一个类),只能尝试手动注入。在网上查了查最终尝试了三种方式,对于我的问题前两种手动注入bean的方式都无法获取到实例,有效的只有方法三,先列出方法,问题分析在后面。
方法
方法一:使用BeanFactory
BeanFactory factory = new ClassPathXmlApplicationContext()("applicationContext.xml");
ac.getBean("beanName");
方法二:使用ApplicationContext
ApplicationContext ac = new ClassPathXmlApplicationContext()("applicationContext.xml");
ac.getBean("beanName");
方法三:实现ApplicationContextAware接口
写一个工具类去实现ApplicationContextAware接口,保存ApplicationContext为静态实例供全局调用。
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.Assert;
public class BeanUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
/**
* 实现ApplicationContextAware接口的context注入函数, 将其存入静态变量.
*/
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
BeanUtil.applicationContext = applicationContext;
}
/**
* 取得存储在静态变量中的ApplicationContext.
*/
public static ApplicationContext getContext() {
checkApplicationContext();
return applicationContext;
}
/**
* 从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型.
*/
@SuppressWarnings("unchecked")
public static <T> T getBean(String name) {
checkApplicationContext();
return (T) applicationContext.getBean(name);
}
/**
* 从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型.
*/
@SuppressWarnings("unchecked")
public static <T> T getBean(Class<T> clazz) {
checkApplicationContext();
System.out.println(clazz);
return (T) applicationContext.getBeansOfType(clazz);
}
/**
* 清除applicationContext静态变量.
*/
public static void cleanApplicationContext() {
applicationContext = null;
}
private static void checkApplicationContext() {
Assert.notNull(applicationContext,
"applicationContext未注入,请在applicationContext.xml中定义BeanUtil");
}
}
还需要在Spring的配置文件中application.xml中手动注册一下这个工具类
<bean id="beanUtil" class="com.xxx.util.BeanUtil" lazy-init="false"></bean>
然后在需要的地方就可以直接调用
TestService testService = BeanUtil.getBean("testService");
问题分析
一开始在使用@Autowired注解无法注入之后,先后尝试了前两种方案,打断点发现service同样是null,没有成功获取到bean,多方尝试之后最终仅有第三种方法成功了。至于问题的成因有过几种猜测:
猜测一: 看到网上有人说由于自己把自动扫包的配置全部放在的SpringMVC的配置文件里,而Spring上下文访问不到SpringMVC的上下文(反之可以),所以get不到bean。
我看了看项目的配置文件,不大可能是这种情况。
猜测二: 在多线程环境下,由于web容器不能感知线程的启动,所以Spring无法向线程中注入。Ps.如果由Spring管理线程池,似乎是可以自动注入的,这部分暂时没有细看,以后再做补充吧。
但是因为不清楚新的项目我做的这部分是否涉及到多线程,无法确定是否该原因。
猜测三: 这部分类没有被Spring管理,所以在类中也无法由Spring去注入,或者采取前两种方案时,由于不在同一个线程中所以获取到的context不是最开始的context,而是new了一个新的。
后两种猜想在本质上是有相似之处的,因为不管是线程还是类,同样都不是由Spring容器去管理,而是在需要的地方去new。经过与人探讨,认为大概率是这种情况。
其他
在上面的解决办法中还有一些需要注意的小细节
1. applicationContext获取bean的时候是可以通过byName或者byType的方式去获取到bean,在我们的工具类中可以看到分别调用了applicationContext的getBean和getBeansOfType方法,这里有一点需要注意,通过byName方式获取到的bean是直接返回的bean的实例,而byType方式则会返回一个Map集合,其中的key是bean的name,value则是bean的实例。我之前没注意到这一点,直接把byType方式的返回结果赋给了bean,结果出现了类型不能转换的错误。
2. BeanFactory和ApplicationContext加载bean的时候有一点不太一样的地方,BeanFactory采用的是延迟加载的方式,第一次用到bean的时候才会加载,而ApplicationContext则是在容器启动的时候一次性创建了所有的bean。它们还有很多其他不同之处,可以参考Spring中ApplicationContext和beanfactory区别。
3. 在手动加载bean的时候,需要注意关闭容器,否则容易造成数据库连接不能释放。推荐使用AbstractApplicationContext,使用完之后调用close方法释放资源
AbstractApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
context.close();//释放资源