Spring IOC 一些常见容易忽略的问题,包括autowire/resource 注入,类型定义,循环依赖等,会后续不断补充。
默认,如果没找到在调用依赖对象时会抛NullPointerException
根据bean class类型在容器中找到唯一一个对应的给目标对象注入
当所注入的类型在容器中只有唯一对象时,较为简单不需要关注属性名或setter方法,以下分析多实现情况
场景1 annotation @Autowired example:
DAO:
public interface UserDAO {
void query();
}
两个DAO实现类(UserDAO1, UserDAO2):
@Repository
public class UserDAO1 implements UserDAO {
@Override
public void query() {
System.out.println("1----User dao query");
}
}
@Repository
public class UserDAO2 implements UserDAO {
@Override
public void query() {
System.out.println("2----User dao query");
}
}
Service调用类(UserService):
@Service
public class UserService {
@Autowired
private UserDAO userDAO1;
public void queryUser(){
userDAO1.query();
}
}
测试代码:
@Configuration
@ComponentScan("com.example.demo.ioc")
public class AutowireDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AutowireDemo.class);
context.getBean(UserService.class).queryUser();
}
}
输出:
1----User dao query
因为在Service中注入的UserDAO有多个实现的情况下,找不到该类型唯一的对象, 再次根据属性名userDAO1判断注入UserDAO1. 当属性名为UserDAO2在容器中的id时,则会注入UserDAO2.
场景2 xml autowire byType example:
xml中配置default-autowire
beans.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd"
default-autowire="byType">
<!--<bean id="userDAO1" class="com.example.demo.ioc.dao.UserDAO1"/>-->
<bean id="userDAO2" class="com.example.demo.ioc.dao.UserDAO2"/>
<bean id="userService" class="com.example.demo.ioc.service.UserService" />
</beans>
UserService:
public class UserService {
private UserDAO userDAO1;
public void queryUser(){
userDAO1.query();
}
public UserDAO getUserDAO1() {
return userDAO1;
}
public void setUserDAO1(UserDAO userDAO) {
this.userDAO1 = userDAO;
}
}
测试代码:
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:beans.xml");
context.getBean(UserService.class).queryUser();
输出:
2----User dao query
以xml方式使用byType方式注入时,要保证所在环境里对应类型的bean唯一性,xml会自动检测编译报出error。此时属性名、setter方法名都不具备任何特殊处理意义,只跟类型有关。
根据name找到容器中唯一id的bean注入给对象.
场景1 annotation方式@Resource 默认将属性名作为name example 1:
当resource不指定name时,则将属性名作为name查找bean,否则按指定name查找bean,与属性名无关
@Service
public class UserService {
@Resource
private UserDAO userDAO2;
public void queryUser(){
userDAO2.query();
}
}
输出:
2----User dao query
@Resource 指定type注入依赖 example 2:
@Resource使用Type属性时,属性名不能与存在的bean Name重合,可能会导致注入对象不一致问题,see @exampl 3
@Service
public class UserService {
@Resource(type = UserDAO2.class)
private UserDAO userDAO;
public void queryUser(){
userDAO.query();
}
}
输出:
2----User dao query
@Resource 指定type同时属性名对应要注入bean的类型不一致 example 3:
属性名指定要注入的为UserDAO1,但同时type指定类型为UserDAO2
@Service
public class UserService {
@Resource(type = UserDAO2.class)
private UserDAO userDAO1;
public void queryUser(){
userDAO1.query();
}
输出:
Error:
警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userService': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'userDAO1' is expected to be of type 'com.example.demo.ioc.dao.UserDAO2' but was actually of type 'com.example.demo.ioc.dao.UserDAO1'
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userService': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'userDAO1' is expected to be of type 'com.example.demo.ioc.dao.UserDAO2' but was actually of type 'com.example.demo.ioc.dao.UserDAO1'
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessPropertyValues(CommonAnnotationBeanPostProcessor.java:321)
场景2 xml配置中配置default-autowire example:
局部为bean单独配置:
<bean id="userService" class="com.example.demo.ioc.service.UserService" autowire="">
beans.xml:
全局配置autowire,指定default-autowire时,不用再单独为bean的property进行显示传值,会自动根据配置的mode初始化bean
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd"
default-autowire="byName">
<bean id="userDAO1" class="com.example.demo.ioc.dao.UserDAO1"/>
<bean id="userDAO2" class="com.example.demo.ioc.dao.UserDAO2"/>
<bean id="userService" class="com.example.demo.ioc.service.UserService">
bean>
beans>
UserService:
注入的UserDAO由setter方法决定,setUserDAO2将容器中名为userDAO2的bean赋给属性userDAO1
public class UserService {
private UserDAO userDAO1;
public void queryUser(){
userDAO1.query();
}
public UserDAO getUserDAO1() {
return userDAO1;
}
public void setUserDAO2(UserDAO userDAO) {
this.userDAO1 = userDAO;
}
}
测试代码:
public class AutowireDemo {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:beans.xml");
context.getBean(UserService.class).queryUser();
}
}
输出:
2----User dao query
与byType相似,按类型注入。需要提供含参构造方法,按照类型查找对应的bean作为参数注入。否则会在使用所依赖的对象是Null Pointer
与no相同
beans.xml
<bean id="prototypeBean" class="com.example.demo.ioc.service.PrototypeBean" scope="prototype">bean>
<bean id="singletonBean" class="com.example.demo.ioc.service.SingletonBean" scope="singleton">bean>
SingletonBean:
public class SingletonBean {
private PrototypeBean prototypeBean;
public void print(){
System.out.println("prototypeBean hashcode:"+prototypeBean.hashCode());
System.out.println("singletonBean hashcode:"+this.hashCode());
}
public void setPrototypeBean(PrototypeBean prototypeBean) {
this.prototypeBean = prototypeBean;
}
}
PrototypeBean:
public class PrototypeBean {
}
测试代码:
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:beans.xml");
for(int i=0;i<3;i++) {
context.getBean(SingletonBean.class).print();
}
输出:
prototypeBean hashcode:1512981843
singletonBean hashcode:42768293
prototypeBean hashcode:1512981843
singletonBean hashcode:42768293
prototypeBean hashcode:1512981843
singletonBean hashcode:42768293
singleton中依赖的原型bean在输出中结果表现为单例.因为在spring初始化单例bean时已经构造并初始化好了依赖对象。每次调用的prototypeBean都是初始化好完成的那个bean。
解决方案
实现该接口,可获取ApplicationContext,在每次调用prototype的时候,从容器中获取即可实现每次即用即建。
但对应的会极大程度引入spring整个环境,不利于解耦
beans.xml:
<bean id="prototypeBean" class="com.example.demo.ioc.service.PrototypeBean" scope="prototype">
bean>
<bean id="singletonBean" class="com.example.demo.ioc.service.SingletonBean" scope="singleton" >
<lookup-method name="getPrototypeBean" bean="prototypeBean">lookup-method>
bean>
SingletonBean:
将获取prototypeBean的方式转为look up method
public abstract class SingletonBean {
public void print(){
System.out.println("prototypeBean hashcode:"+getPrototypeBean().hashCode());
System.out.println("singletonBean hashcode:"+this.hashCode());
}
public void setPrototypeBean(PrototypeBean prototypeBean) {
this.prototypeBean = prototypeBean;
}
public abstract PrototypeBean getPrototypeBean();
}
lookup method由spring负责解析返回对应的一个bean,不需要程序员手动实现,因此可作为抽象类存在与配置
输出:
prototypeBean hashcode:668849042
singletonBean hashcode:434176574
prototypeBean hashcode:2096057945
singletonBean hashcode:434176574
prototypeBean hashcode:1689843956
singletonBean hashcode:434176574