Spring|IOC与DI
一、IOC
IOC(Inversion of Control),控制反转,是Spring的核心内容之一。
什么是“控制反转”?
【示例】
package com.my; /** * @Author jyy * @Description {} * @Date 2018/7/16 11:29 */ public class Person { private String id; private String name; private Address address; public Person() { this.id = "1001"; this.name = "张三"; this.address = new Address(); } }
如上定义了类Person,并在构造函数中对其属性进行赋值。这种方式虽然简单,但是代码的重用性不强,而且耦合度很高,所以我们可以做如下更改:
package com.my; /** * @Author jyy * @Description {} * @Date 2018/7/16 11:29 */ public class Person { private String id; private String name; private Address address; public Person(String id, String name, Address address) { this.id = id; this.name = name; this.address = address; } }
这种方式,将类中属性赋值的权利,交由第三方。提高了代码的重用性,并降低了耦合度。基于这个思想,Spring为我们提供了另一种更加灵活的方式,代码如下:
package com.my; /** * @Author jyy * @Description {} * @Date 2018/7/16 11:29 */ public class Person { private String id; private String name; private Address address; public Person(String id, String name, Address address) { this.id = id; this.name = name; this.address = address; } public Person() { } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } }
package com.my; /** * @Author jyy * @Description {} * @Date 2018/7/16 11:30 */ public class Address { private String country; private String province; private String city; public String getCountry() { return country; } public void setCountry(String country) { this.country = country; } public String getProvince() { return province; } public void setProvince(String province) { this.province = province; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } }
xml配置
<bean id="person" class="com.my.Person"> <constructor-arg type="java.lang.String" value="1001"/> <constructor-arg type="java.lang.String" value="张三"/> <constructor-arg type="com.my.Address" ref="address"/> </bean> <bean id="address" class="com.my.Address"> <property name="country" value="中国"/> <property name="province" value="江苏省"/> <property name="city" value="南京市"/> </bean>
package com.my; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * @Author jyy * @Description {} * @Date 2018/7/13 10:06 */ public class MainApp { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml"); Person person = (Person) context.getBean("person"); System.out.println(person.getName()); Address address = person.getAddress(); System.out.println(address.getCountry() + "-" + address.getProvince() + "-" + address.getCity()); } }
执行结果:
张三
中国-江苏省-南京市
Spring提供的这种方式将传统上由程序代码直接操控的对象的调用权交给外部容器,通过容器来实现对象组件的装配和管理。
所谓的“控制反转”概念就是组件对象的控制权转移了,从程序代码本身转移到了外部容器。
IOC最常见的一种应用场景,就是配置数据库连接。我们将操作数据库的对象交由容器进行统一管理。
二、IOC容器
Spring容器是Spring框架的核心,容器创建对象,把它们连接在一起,配置它们,并管理他们的整个生命周期从创建到销毁。Spring 容器使用依赖注入(DI)来管理组成一个应用程序的组件。这些被创建的对象被称为Spring Beans。
通过阅读配置元数据提供的指令,容器知道对哪些对象进行实例化,配置和组装。配置元数据可以通过 XML或 Java 代码(参考3.4)来表示。
Spring IOC容器利用Java的POJO类和配置元数据来生成Spring Beans。
2.1、IOC容器-BeanFactory
这是一个最简单的容器,它主要的功能是为依赖注入 (DI) 提供支持,这个容器接口在 org.springframework.beans.factory.BeanFactory 中被定义。 BeanFactory 和相关的接口,比如BeanFactoryAware、 DisposableBean、InitializingBean,仍旧保留在 Spring 中,主要目的是向后兼容已经存在的和那些 Spring 整合在一起的第三方框架。
在 Spring 中,有大量对 BeanFactory 接口的实现。其中,最常被使用的是 XmlBeanFactory 类。这个容器从一个 XML 文件中读取配置元数据,由这些元数据来生成一个被配置化的系统或者应用。
在资源宝贵的移动设备或者基于 applet 的应用当中, BeanFactory 会被优先选择。否则,一般使用的是 ApplicationContext,除非你有更好的理由选择 BeanFactory。
【示例】
XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("Beans.xml")); Person person = (Person) factory.getBean("person"); System.out.println(person.getName());
2.2、IOC容器-ApplicationContext
Application Context 是 spring 中较高级的容器。和 BeanFactory 类似,它可以加载配置文件中定义的 bean,将所有的 bean 集中在一起,当有请求的时候分配 bean。 另外,它增加了企业所需要的功能,比如,从属性文件中解析文本信息和将事件传递给所指定的监听器。这个容器在 org.springframework.context.ApplicationContext interface接口中定义。
ApplicationContext 包含 BeanFactory 所有的功能,一般情况下,相对于 BeanFactory,ApplicationContext 会更加优秀。当然,BeanFactory 仍可以在轻量级应用中使用,比如移动设备或者基于 applet 的应用程序。
最常被使用的 ApplicationContext 接口实现:
-
FileSystemXmlApplicationContext:该容器从 XML 文件中加载已被定义的 bean。在这里,你需要提供给构造器 XML 文件的完整路径。
-
ClassPathXmlApplicationContext:该容器从 XML 文件中加载已被定义的 bean。在这里,你不需要提供 XML 文件的完整路径,只需正确配置 CLASSPATH 环境变量即可,因为,容器会从 CLASSPATH 中搜索 bean 配置文件。
-
WebXmlApplicationContext:该容器会在一个 web 应用程序的范围内加载在 XML 文件中已被定义的 bean。
//负责生成和初始化所有的对象,即所有在 XML bean 配置文件中的 bean。 ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml"); //利用 getBean() 方法得到所需要的 bean。 这个方法通过配置文件中的 bean ID 来返回一个真正的对象。一旦得到这个对象,就可以利用这个对象来调用任何方法。 Person person = (Person) context.getBean("person"); System.out.println(person.getName()); Address address = person.getAddress(); System.out.println(address.getCountry() + "-" + address.getProvince() + "-" + address.getCity());
三、DI
DI(Dependency Injection),依赖注入,控制反转(IOC)是一个通用的概念,它可以用许多不同的方式去表达,依赖注入仅仅是控制反转的一个具体的例子。
3.1、什么是依赖注入?
让我们将这两个词分开来看一看。这里将依赖关系部分转化为两个类之间的关联。例如,类 A 依赖于类 B。现在,让我们看一看第二部分,注入。这意味着类 B 将通过 IOC 被注入到类 A 中。
依赖注入可以向构造函数传递参数的方式发生,或者通过使用 setter 方法 post-construction。
【举例】
在第一个示例中,我们已经使用构造函数进行依赖注入,下面我们说第二种方法:
package com.my; /** * @Author jyy * @Description {} * @Date 2018/7/16 11:29 */ public class Person { private String id; private String name; private Address address; public Person() { } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } }
package com.my; /** * @Author jyy * @Description {} * @Date 2018/7/16 11:30 */ public class Address { private String country; private String province; private String city; public String getCountry() { return country; } public void setCountry(String country) { this.country = country; } public String getProvince() { return province; } public void setProvince(String province) { this.province = province; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } }
<?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-3.0.xsd"> <bean id="person" class="com.my.Person"> <property name="id" value="1001"/> <property name="name" value="张三"/> <property name="address" ref="address"/> </bean> <bean id="address" class="com.my.Address"> <property name="country" value="中国"/> <property name="province" value="江苏省"/> <property name="city" value="南京市"/> </bean> </beans>
package com.my; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * @Author jyy * @Description {} * @Date 2018/7/13 10:06 */ public class MainApp { public static void main(String[] args) { //负责生成和初始化所有的对象,即所有在 XML bean 配置文件中的 bean。 ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml"); //利用 getBean() 方法得到所需要的 bean。 这个方法通过配置文件中的 bean ID 来返回一个真正的对象。一旦得到这个对象,就可以利用这个对象来调用任何方法。 Person person = (Person) context.getBean("person"); System.out.println(person.getName()); Address address = person.getAddress(); System.out.println(address.getCountry() + "-" + address.getProvince() + "-" + address.getCity()); } }
输出结果:
张三
中国-江苏省-南京市
两种方式的返回结果一致
3.2、自动装配-依赖注入
Spring 容器可以在不使用<constructor-arg>和<property>元素的情况下自动装配相互协作的 bean 之间的关系,这有助于减少编写一个大的基于 Spring 的应用程序的 XML 配置的数量。
其中较常用的两种方式,“byName”和“byType”
【举例】
“byName”,配置文件中的属性autowire=“byName”,并且Person类中包含属性address,及其setAddress(..)方法,那么spring就会查找配置文件中id=“address”的bean进行自动装配。
<?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-3.0.xsd"> <bean id="person" class="com.my.Person" autowire="byName"> <property name="id" value="1001"/> <property name="name" value="张三"/> </bean> <bean id="address" class="com.my.Address"> <property name="country" value="中国"/> <property name="province" value="江苏省"/> <property name="city" value="南京市"/> </bean> </beans>
“byType”,配置文件中的属性autowire=“byType”,并且Person类中包含属性address,及其setAddress(..)方法。address由Address类声明,那么spring就会查找配置文件中类型为Address类的bean进行自动装配。
<?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-3.0.xsd"> <bean id="person" class="com.my.Person" autowire="byType"> <property name="id" value="1001"/> <property name="name" value="张三"/> </bean> <bean id="address" class="com.my.Address"> <property name="country" value="中国"/> <property name="province" value="江苏省"/> <property name="city" value="南京市"/> </bean> </beans>
3.3、注解装配-依赖注入
从 Spring 2.5 开始就可以使用注解来配置依赖注入。注解默认情况下在 Spring 容器中不打开。因此,在可以使用基于注解之前,我们将需要在我们的 Spring 配置文件中启用它。
<!--开启注解--> <context:annotation-config/>
@Required:作用于属性的setter方法,标明属性必须在配置文件中声明,否则会抛出异常。
@Required public void setAddress(Address address) { this.address = address; }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--开启注解--> <context:annotation-config/> <bean id="person" class="com.my.Person"> <property name="id" value="1001"/> <property name="name" value="张三"/> <!--<property name="address" ref="address"/>--> </bean> <bean id="address" class="com.my.Address"> <property name="country" value="中国"/> <property name="province" value="江苏省"/> <property name="city" value="南京市"/> </bean> </beans>
输出结果:抛出“BeanInitializationException”异常
@Autowired:可以作用于类的构造方法,属性的setter方法及属性本身。利用“byType”模式,实现属性值的自动装配。
package com.my; import org.springframework.beans.factory.annotation.Autowired; /** * @Author jyy * @Description {} * @Date 2018/7/16 11:29 */ public class Person { private String id; private String name; private Address address; public Person() { } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Address getAddress() { return address; } @Autowired public void setAddress(Address address) { this.address = address; } }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--开启注解--> <context:annotation-config/> <bean id="person" class="com.my.Person"> </bean> <bean id="address" class="com.my.Address"> <property name="country" value="中国"/> <property name="province" value="江苏省"/> <property name="city" value="南京市"/> </bean> </beans>
执行结果:
null null 中国-江苏省-南京市
虽然配置文件中,没有配置address属性与com.my.Address之间的关联关系,但是通过注解@Autowired,实现属性的自动装配。
我们也可以在属性本身及类构造函数上声明@Autowired,例如:
@Autowired private Address address;
@Autowired public Person(Address address) { this.address = address; }
@Qualifier:当创建多个类型相同的bean时,@Autowired就不能唯一确定该调用哪个bean进行自动装配,这个时候就需要@Qualifier来消除混乱。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--开启注解--> <context:annotation-config/> <bean id="person" class="com.my.Person"> </bean> <bean id="address1" class="com.my.Address"> <property name="country" value="中国"/> <property name="province" value="江苏省"/> <property name="city" value="南京市"/> </bean> <bean id="address2" class="com.my.Address"> <property name="country" value="中国"/> <property name="province" value="江苏省"/> <property name="city" value="苏州市"/> </bean> </beans>
@Autowired @Qualifier("address2") private Address address;
执行结果:
中国-江苏省-苏州市
@Resource:区别于上面的注解,此注解是jdk提供的,不是spring的特性。可以作用于属性及其setter方法,利用“byName”模式,实现属性值的自动装配。
@Resource(name="address1") private Address address;
执行结果:
中国-江苏省-南京市
也可以直接使用 @Resource,而不声明name,这时就通过属性名进行查找。
3.4、基于java的配置元数据
以上的配置均是基于XML配置元数据,下面我们将演示如何通过代码及注解完成元数据配置。
package com.my; /** * @Author jyy * @Description {} * @Date 2018/7/16 11:29 */ public class Person { private String id; private String name; private Address address; public Person() { } public Person(Address address) { this.address = address; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } }
package com.my; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @Author jyy * @Description {} * @Date 2018/8/2 17:49 */ @Configuration public class AnnotationConfig { @Bean public Person person() { return new Person(address()); } @Bean public Address address() { return new Address("中国", "江苏", "无锡"); } }
package com.my; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; /** * @Author jyy * @Description {} * @Date 2018/7/13 10:06 */ public class MainApp { public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(AnnotationConfig.class); Person person = ctx.getBean(Person.class); Address address = person.getAddress(); System.out.println(address.getCountry() + "-" + address.getProvince() + "-" + address.getCity()); } }
输出结果:
中国-江苏-无锡
带有 @Configuration 的注解类表示这个类可以使用 Spring IoC 容器作为 bean 定义的来源。@Bean 注解告诉 Spring,一个带有 @Bean 的注解方法将返回一个对象,该对象应该被注册为在 Spring 应用程序上下文中的 bean。
在这里,带有 @Bean 注解的方法名称作为 bean 的 ID,它创建并返回实际的 bean,比如address()中的“address”为bean的ID。