DI(Dependency Injection):依赖注入
bean依赖关系的管理交由Spring进行维护,当bean对象要用到其他类的对象时,由Spring提供,我们只需在配置文件中说明即可。
能注入的数据:
基本类型和String其他bean类型(已配置的)复杂类型或集合注入的方式:
Set方法注入构造方法注入p命名空间注入(实质为Set方法注入,属性内嵌在bean标签内进行配置达到更简洁,详情见官方文档)c命名空间注入(实质为构造方法注入,属性内嵌在bean标签内进行配置达到更简洁,详情见官方文档)通过该类的构造方法进行注入,在bean中使用的是constructor-arg标签,其属性有:
type:注入的数据类型,也是构造方法中某个参数的类型index:构造方法中指定参数列表的索引位置的参数赋值name:构造方法中指定名称的参数赋值(常用)value:提供基本类型和String类型的数据ref:指定其他Bean类型的数据,该Bean在容器中必须已经配置过,其值为其他bean的id值。首先创建一个实体类:
public class User { private int id; private String name; private Boolean sex; private Date date; public User(int id, String name, Boolean sex, Date date) { this.id = id; this.name = name; this.sex = sex; this.date = date; } }在配置文件中进行配置:
<bean id="user" class="org.lhy.pojo.User"> <constructor-arg name="id" index="0" value="1"/> <constructor-arg name="name" index="1" value="zhangsan"/> <constructor-arg name="sex" index="2" value="true" type="java.lang.Boolean"/> <constructor-arg name="date" index="3" ref="now" /> </bean> <bean id="now" class="java.util.Date" />注意:
我们发现在对bean进行配置时,如果不进行注入会报错,其原因是bean的默认创建方法为无参构造方法进行创建,而User中无参构造方法已被代替了,所以会报错。同时,如果你注入的数据与构造方法中参数列表中的数据类型不符或缺少,在运行时也会报错,这也是构造方法注入不灵活的劣势。 优点:获取Bean对象时,注入数据是必要的,否则无法创建成功。缺点:固化了Bean对象的实例化方式,使我们在创建对象时,必须提供构造方法所需的所有数据,即使用不到该数据。通过该类的set方法进行注入,在bean中使用的是property标签,其属性有:
name:指定注入时所调用的set方法名称value:提供基本类型和String类型的数据ref:指定其他Bean类型的数据,该Bean在容器中必须已经配置过首先创建一个实体类:
public class People { private String name; private int age; private Date birth; public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } public void setBirth(Date birth) { this.birth = birth; } }在配置文件中进行配置:
<bean id="people" class="org.lhy.pojo.People"> <property name="age" value="23"/> <property name="birth" ref="now"/> <property name="name" value="张三"/> </bean> <bean id="now" class="java.util.Date" /> 优点:创建对象时没有明确限制,可直接且必须使用默认构造方法。缺点:如果某个成员必须有值,则set方法注入方式无法保证该成员一定被注入。常见集合有数组、List、Set、Map、Properties等类型,这里我们用最常用的set注入进行数据注入。
用于给List结构集合注入的标签有:list、array、set用于给Map结构集合注入的标签有:map、props结构相同,标签是可以互换的
创建一个实体类:
public class MyList { private String[] arr; private List<String> list; private Set<String> set; private Map<String,String> map; private Properties properties; public void setArr(String[] arr) { this.arr = arr; } public void setList(List<String> list) { this.list = list; } public void setSet(Set<String> set) { this.set = set; } public void setMap(Map<String, String> map) { this.map = map; } public void setProperties(Properties properties) { this.properties = properties; } }在配置文件中进行配置:
<bean id="myList" class="org.lhy.pojo.MyList"> <property name="arr"> <array> <value>aaa</value> <value>bbb</value> </array> </property> <property name="list"> <list> <value>aaa</value> <value>bbb</value> </list> </property> <property name="set"> <set> <value>aaa</value> <value>bbb</value> </set> </property> <property name="map"> <map> <entry key="a" value="aaa"/> <entry key="b"> <value>bbb</value> </entry> </map> </property> <property name="properties"> <props> <prop key="a">aaa</prop> <prop key="b">bbb</prop> </props> </property> </bean>大意是,由于可以混合使用set注入和构造方法注入,所以建议将构造方法注入用于必须注入的依赖项(属性),将set注入用于非必须注入的依赖项(属性)。
虽然在set注入中,可以使用@Required注解使其属性成为必须的注入的属性,但是,最好使用构造方法注入。
Spring团队提倡构造方法注入,因为它可以使bean实现为不可变对象,并确保其必要属性都不为null。
在property或constructor-arg标签中还可以嵌套bean标签实现内部bean。
<bean id="user" class="..."> <property name="address"> <bean class="..."> <property name="..." value="..."/> </bean> </property> </bean>使用内部bean并不需要id标识符,因为它始终是匿名的,并且只能被外部bean所创建和引用,不能被独立使用和其他地方引用。同时,容器在创建时也会忽略内部bean所定义的作用域。
空字符串和Null值空字符串和Null值的注入。
<bean class="ExampleBean"> <property name="email" value=""/> </bean> <bean class="ExampleBean"> <property name="email"> <null/> </property> </bean> 父模板bean你可以在容器中定义一个抽象的模板bean,另一个bean通过使用parent属性实现继承父模板bean。
<bean id="animal" abstract="true" class="....animal"> <property name="name" value="parent"/> <property name="age" value="1"/> </bean> <bean id="dog" class="....dog" parent="animal"> <property name="name" value="override"/> </bean>子bean可以继承父bean的属性和设置,也可以对其重写。如果子bean没有定义类型,则子类将会使用父bean的类型,前提是必须兼容(即子类能够接受父bean的属性)
抽象的父bean不能单独实例化,因为它是不完整的,只能作为模板bean供其他bean使用,因此也可以不指定其类型。
depends-no跟ref都是表示对其他bean的依赖,通常ref是表示作为一个该bean属性的依赖,依赖关系较强。但有时依赖关系并不需要太直接,就可以使用depends-on来控制Bean加载顺序。
仅当一个Bean并不是通过构造方法或属性显式的依赖另一个Bean,而是依赖于另一个Bean的初始化的时候使用depends-on。
<bean id="a" class="AClass" depends-on="b"/> <bean id="b" class="BClass" />举一个例子,A类是作为一个资源提供类,存放着许多静态属性作为配置参数,B类在创建时需要A类中的配置参数作初始化,那么就可以使用depends-on使A类优先加载。
<bean id="aClass" class="AClass"/> <bean id="bClass" class="BClass" depends-on="aClass"/>默认情况下,ApplicationContext容器初始化时采用立即加载所有的单例bean,但有时候业务需求并不需要立即加载,这时候我们可以使用lazy-init属性来开启延迟加载,来规定该bean被使用时才进行创建,而非容器初始化时创建。
<bean id="..." class="..." lazy-init="true"/>但是我们需注意,如果一个延迟加载的bean被立即加载的bean所依赖时,延迟加载的bean也会被立即加载。
Spring容器可以自动装配bean之间的依赖,通过容器配置,可以让容器自动装配bean的依赖项(其他bean)。
当使用XML配置方式时,我们可以通过autowire属性选择自动装配模式,无需再写相关属性,自动装配有四种模式,但不建议修改默认设置。
模式含义no(默认)无自动装配,bean引用必须用ref标签,对于大型部署,不建议更改默认设置。byName通过字段名(限定符、bean名)进行自动装配。byType通过类型进行自动装配。如果容器中存在该类型唯一的bean,则会进行自动装配;如果存在一个以上,就会出现异常;如果没有匹配,则为null。constructor类似于byType,但适用于构造方法参数,若容器中不存在构造方法参数类型的bean,则会出现异常。优点:
自动装配可以显著减少指定属性或构造方法参数的需要。自动装配可以随着bean的演化而更新配置。缺点:
由显式的属性和构造方法配置的依赖项(即要自动装配的bean)总是覆盖自动装配。你无法自动装配简单属性,这种限制是设计造成的。自动装配不如显式配置精确。如byType中可能会匹配到多个依赖项。对Spring自动生成文档的工具不太友好。其他方案:
放弃自动装配通过autowire-candidate属性设置为false来避免此bean被其他bean自动装配。通过primary属性设置为true来使此bean作为自动装配的首选,来避免多个匹配造成的异常。使用基于注解自动装配策略。(优选)自动装配相关属性汇总:
autowire:选择自动装配选择模式autowire-candidate:排除在自动装配匹配范围之外primary:开启后作为自动装配的首选大多数场景下,容器的bean为单例。当一个单例bean需要另一个单例bean或一个非单例bean需要另一个非单例bean协作时,通常我们将一个bean作为另一个bean的属性来处理此依赖。但是如果两个bean的生命周期不同时就会出现问题。
例如:单例beanA需要多例beanB,容器仅创建一次beanA,所以也只会创建一次beanB,这会造成容器在每次使用beanB时,beanA都不会为创建一个新的beanB实例。
这里我们可以使用接口ApplicationContextAware来获取容器对象,实现每次使用beanB时,通过调用getBean()方法来创建新的beanB实例。
还有一种接口BeanNameAware 是来直接获取bean对象的,用法和ApplicationContextAware相似,详见官方文档
现通过以下案例进行测试。
XML配置:
<bean id="userDao" class="org.lhy.dao.UserDao" scope="prototype"/> <bean id="userService" class="org.lhy.service.UserService"/>类配置:
public class UserService implements ApplicationContextAware { private ApplicationContext applicationContext; public void test(){ System.out.println(applicationContext.getBean("userDao")); } public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } public static void main(String[] args) { ApplicationContext ac = new ClassPathXmlApplicationContext("springconfig.xml"); UserService userService = (UserService) ac.getBean("userService"); userService.test(); userService.test(); } /**输出结果 org.lhy.dao.UserDao@39c0f4a org.lhy.dao.UserDao@1794d431 */ }上述使用ApplicationContextAware接口的方法虽然可以实现案例需求,但是这不遵守控制反转的风格,并且可能会影响生命周期机制(xxxAware相关接口还有很多),因此Spring提倡使用方法注入来完成相关需求。
你可以通过这篇博客查看更多有关Method Injection的细节。
你也可以通过lookup-method标签实现方法注入,容器通过重写bean的方法来注入另一个bean,通常用于多例。其原理是Spring框架通过CGLib库的字节码动态生成能够覆盖这个方法的子类来实现方法注入。
注意:
使用这种方法注入,其覆盖方法的子类不允许是final,且覆盖方法也不允许是final。对具有抽象方法的类进行单元测试,要手动实现子类,并实现抽象方法。这种方法注入还不能与工厂方法、配置类的@Bean方法一起进行。一共有三种形式实现:
XML配置: <bean id="userDao" class="org.lhy.dao.UserDao" scope="prototype"/> <bean id="userService" class="org.lhy.service.UserService"> <lookup-method name="createUserDao" bean="userDao"/> </bean> public abstract class UserService{ public void test(){ System.out.println(createUserDao()); } protected abstract UserDao createUserDao(); }要覆盖的方法须要以下形式的签名:
<public|protected> [abstract] <return-type> theMethodName(no-arguments); 基于注解第一种形式: public abstract class UserService{ public void test(){ System.out.println(createUserDao()); } @Lookup("userDao") protected abstract UserDao createUserDao(); } 基于注解第二种形式:通过覆盖方法的返回类型进行解析。
public abstract class UserService{ public void test(){ System.out.println(createUserDao()); } @Lookup protected abstract UserDao createUserDao(); }