Spring依赖注入总结
2017-11-14 00:00
393 查看
Spring依赖注入总结
0.为什么我们需要依赖注入
因为我们的应用要尽可能地做到高内聚,低耦合。既然我们需要提供解耦,而解耦最重要的原则就是依赖倒置原则(Dependence Inversion Principle):高层模块不应该依赖底层模块,他们都应该依赖抽象。抽象不应该依赖于细节,细节应该依赖于抽象。那既然高层模块和底层模块都依赖于抽象了,这就带来了依赖倒置的概念,比方说,底层模块依赖于高层模块对某个接口的实现。这就又引出了控制反转(Inversion of Control)的概念,即底层模块(服务端)去回调高层模块(客户端),程序的控制权反转到了高层模块手中。1.什么是依赖注入
我们知道,A对象依赖于B对象,等价于A对象内部存在对B对象的“调用”,亦即A对象内部拿到了B对象的引用。B对象的引用的来源无非有以下两种:A对象内部创建(无论是作为字段还是作为临时变量)、从外部注入,即依赖注入。依赖注入(Dependency Injection),又名控制反转,意为某一接口具体实现类的选择控制权从调用类中移除,转交给第三方决定。换句话说,让调用类对某一接口实现类的依赖关系由第三方(容器或协作类)注入,以移除调用类对某一接口实现类的依赖。
抛开具体接管依赖注入功能的容器不谈,我们先举两个简单的例子来展示一下依赖注入几种方式。第三方容器要做的,无非是去实现这几种注入方式,设法将调用类所依赖的实现类传递给调用类。
1.1 构造函数注入
我们可以在调用类通过构造函数初始化的时候,将接口实现类通过构造方法变量传入。这就需要调用类提供一个以接口实现类为参数的构造方法,如下所示,MoAttack可以通过传入Geli实现类来构造:
public class MoAttack { private GeLi geli; public MoAttack(GeLi geli) { this.geli = geli; } public void cityGateAsk() { geli.responseAsk("墨者革离"); } }
这样,第三方Director类就可以负责为MoAttack类注入geli属性:
public class Director { public void direct(){ //创建Geli实现类 GeLi geli = new LiuDeHua(); //注入Geli实现类 MoAttack moAttack = new MoAttack(geli); moAttack.cityGateAsk(); } }
我们可以看到,这波操作使得MoAttack类不必去在自己内部去实现geli成员,而是由Director类代劳了,并通过MoAttack类的构造方法注入进了MoAttack类中。
1.2 属性注入
结合例子,我们可以发现,构造方法注入的一个弊端是,MoAttack的geli成员的引用在类创建时就被决定了,要是我之后想更改的话,要么重新创建一次MoAttack类,要么在内部创建一个新类替换geli成员原来的引用,那我之前就相当于白注入了。所以为了更灵活地进行依赖注入,我们可以采用属性注入的方式:
public class MoAttack { private GeLi geli; public void setGeli(GeLi geli) { this.geli = geli; } public void cityGateAsk() { geli.responseAsk("墨者革离"); } }
MoAttack为geli属性提供了一个setter方法,这样任何时候,Director类都可以在需要的时候为MoAttack注入geli属性的值:
public class Director { public void direct(){ GeLi geli = new LiuDeHua(); MoAttack moAttack = new MoAttack(); //注入Geli实现类 moAttack.setGeli(geli); moAttack.cityGateAsk(); } }
我们可以看到,虽然MoAttack类和LiuDeHua类实现了解耦,但Geli类的实例化工作无非是被转移到了Director类中而已。我们希望能够将这些类的初始化、装配工作交给一个“第三方”来办理,从而让开发者从这些底层实现类的实例化、依赖关系装配等工作中解脱出来,专注于更有意义的业务逻辑开发工作。而Spring则是提供了这样一套依赖注入、java bean管理的容器。
借助于java语言提供的反射特性,Spring可以让开发者使用简单的配置文件完成依赖注入。因此,在介绍Spring依赖注入的基本用法之前,我们先简单回顾一下java的反射机制。
2.java反射简介
Java语言为我们提供了一套反射API,所谓“反射(Reflection)”,就如同通过镜子看一个对象的倒影。通俗点将,反射API为我们提供了程序运行时获取程序在运行时刻的内部结构,在知道了java类在运行时候的内部结构之后,就可以与其交互,包括创建新的对象和调用对象中的方法等。反射API提供了运行时的灵活性,但代价就是性能比较差,即使jvm针对此做了优化。Java 反射API的第一个主要作用是获取程序在运行时刻的内部结构,我们知道java中,每当编写并编译了一个新类,类加载器就会为这个类产生一个Class对象,它包含了与类有关的信息。Class类与java.lang.reflect类库一起对反射的概念进行了支持,其本质在于jvm在运行时打开和检查.class文件。只要有了java.lang.Class类的对象,就可以通过其提供的getFields()、getMethods()和getConstructors()来获取到该类中表示域、方法和构造方法的对象的数组,这样,匿名对象的类信息就可以在运行时被完全确定下来,而在编译时不需要知道任何事情。
反射API的另一个作用是在运行时刻对一个java对象进行操作,这些操作包括动态创建一个Java类的对象,获取某个域的值以及调用某个方法。在Java源代码中编写的对类和对象的操作,都可以在运行时刻通过反射API来实现。
3.如何使用Spring进行依赖注入
现在,我们来看具体如何通过Spring来实现依赖注入的。Spring支持属性注入、构造函数注入、方法注入等方式,并且提供了我们通过xml配置文件、注解等来定义Bean的属性。3.1 属性注入
属性注入要求Bean提供一个默认的构造函数,并为需要注入的属性提供对应的setter方法。Spring先调用Bean的不带参数的默认构造函数实例化Bean对象,然后通过反射的方式调用Setter方法注入属性值。
public class Car { private int maxSpeed; public String brand; private double price; public String getBrand() { return brand; } public void setBrand(String brand) { this.brand = brand; } public int getMaxSpeed() { return maxSpeed; } public void setMaxSpeed(int maxSpeed) { this.maxSpeed = maxSpeed; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public String toString() { return "brand:" + brand + "/maxSpeed:" + maxSpeed + "/price:" + price; } }
接下来,我们通过Spring配置文件对Car进行属性注入
<?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-4.0.xsd"> <bean id="car" class="com.smart.attr.Car" lazy-init="default"> <property name="brand"> <value> <![CDATA[红旗&CA72]]> </value> </property> <property name="maxSpeed"> <value>200</value> </property> <property name="price" value="2000.00" /> </bean> </beans>
上述配置为Car这个Bean的maxSpeed, brand, price三个属性注入了对应的值。配置生效的前提是Car类中提供了这三个属性对应的setter方法。
3.2 构造函数注入
Spring当然也支持我们通过构造函数的方式注入,这种注入方式可以保证一些必要的属性在Bean实例化的时候就得到设置,确保Bean实例化后就可以使用。
使用构造函数注入的前提是Bean必须提供带参的构造函数:
public class Car { public String brand; private String corp; private double price; private int maxSpeed; public Car() {} public Car(String brand, double price) { this.brand = brand; this.price = price; } public Car(String brand, String corp, double price) { this.brand = brand; this.corp = corp; this.price = price; } public Car(String brand, String corp, int maxSpeed) { this.brand = brand; this.corp = corp; this.maxSpeed = maxSpeed; } public String getBrand() { return brand; } public void setBrand(String brand) { this.brand = brand; } public int getMaxSpeed() { return maxSpeed; } public void setMaxSpeed(int maxSpeed) { this.maxSpeed = maxSpeed; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public String toString(){ return "brand:"+brand+"/maxSpeed:"+maxSpeed+"/price:"+price; } }
对应的xml配置文件如下:
<bean id="car1" class="com.smart.ditype.Car"> <constructor-arg type="java.lang.String"> <value>红旗CA72</value> </constructor-arg> <constructor-arg type="double"> <value>20000</value> </constructor-arg> </bean>
通过配置元素属性,即可以完成将指定值作为构造函数参数注入。针对区分不同重载构造方法,Spring提供了按类型匹配入参、按索引匹配入参、通过自身类型反射匹配入参,也支持联合使用类型和索引匹配入参的配置方式。
3.3 工厂方法注入
Spring IOC容器以框架的方式提供工厂方法的功能,并以透明的方式开放给开发者,同时,Spring自然也支持开发者自己使用工厂方法注入。如果工厂方法是非静态的,那么必须实例化工厂类后才能调用工厂方法。而如果工厂方法是静态的,则用户在无需创建工厂类实例的情况下就可以调用工厂类方法。所以,静态工厂方法一般比非静态工厂更易用。
我们可以试图创建一个静态工厂方法:
public class CarFactory { public Car createHongQiCar(){ Car car = new Car(); car.setBrand("红旗CA72"); return car; } public static Car createCar(){ Car car = new Car(); return car; } }
因为静态工厂方法使得我们无需创建工厂类实例,所以配置文件中,我们只需要直接在中通过class属性指定工厂类,然后再通过factory-method指定对应的工厂方法即可:
<bean id="car6" class="com.smart.ditype.CarFactory"
如果是非静态工厂方法,则用户还需要在配置文件中定义工厂类的Bean:
<bean id="carFactory" class="com.smart.ditype.CarFactory" /> <bean id="car5" factory-bean="carFactory" factory-method="createHongQiCar">
3.4 方法注入
3.5 基于注解的配置
Spring容器成功启动,需要我们同时提供Bean定义信息和Bean实现类,使用基于xml的配置,则Bean定义信息和Bean实现类本身是分离的。Spring也支持基于注解的配置,如果采用基于注解的配置文件,则Bean定义信息通过在Bean实现类上标注注解实现。
Spring提供了@Component、@Repository、@Service、@Controller四个注解,其中@Component用于标注普通的POJO,@Repository用于对DAO实现类进行标注,@Service用于对Service实现类进行标注,@Controller用于对Controller实现类进行标注。
如下所示,我们通过Spring注解将UserDao类标记为一个Bean
@Component("userDao") public class UserDao { }
Spring容器会识别出该类并自动将其转化为容器管理的Bean。
同时,Spring提供了一个context命名空间,其提供了通过扫描类包以应用注解定义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" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd" > <context:component-scan base-package="com.smart.anno"/> </beans>
声明context命名空间后,接下来即可以通过context命名空间的component-scan的base-package属性指定一个需要扫描的基类包,Spring容器将会扫描这个基类包里的所有类,并从类的注解信息里获取Bean的定义信息。
Spring也提供了灵活的类过滤支持,我们可以自定义过滤表达式来进行类过滤。具体的过滤表达式类型和编写方法可以参考Spring官方文档。
3.6 自动装配Bean
Spring通过@Autowired注解实现Bean的依赖注入。如下所示,@Service将LogonService标注为一个Bean,并通过@Autowired注入LogDao以及UserDao的Bean。@Autowired默认按类型匹配的方式在容器中查找有且仅有一个匹配的Bean,查找到后,Spring将其注入@Autowired标注的变量中。
@Service public class LogonService implements BeanNameAware { @Lazy @Autowired(required=false) private LogDao logDao; @Autowired @Qualifier("userDao") private UserDao userDao; public void saveLog(){ logDao.saveLog(); }
我们也可以看到,可以使用@Qualifier注解限定Bean的名称。使用@Lazy注解可以使得Spring容器在启动的时候不会立即注入属性值,而是延迟到调用次属性的时候才注入属性值。
@AutoWired属性还可以对类成员变量及方法的入参进行标注,并允许对方法入参标注@Qualifier已指定注入Bean的名称。更多相关内容可参考Spring官方文档。
4.引申:框架和类库的区别
使用开发框架时,框架掌握程序流程的控制权,而使用类库时,则是应用程序掌握程序流程的控制权。或者说,使用框架时,程序的主循环位于框架中,而使用类库时,程序的主循环位于应用程序之中。框架会回调应用程序,而类库则不会回调应用程序。5. Ref
代码均摘自《精通Spring 4.x企业应用开发实战》https://www.zhihu.com/question/20821697
相关文章推荐
- spring依赖注入总结
- Spring依赖注入:注解注入总结
- Spring依赖注入:注解注入总结
- Spring依赖注入:注解注入总结
- Spring依赖注入:注解注入总结
- Spring 学习总结(一)依赖注入的理解
- Spring 学习总结(一)依赖注入的理解
- Spring依赖注入:注解注入总结
- spring 依赖注入方式总结详解
- 实习总结(十)---Spring的下载/安装/优点/依赖注入方式/自动配置
- Spring依赖注入:注解注入总结
- Spring学习总结1(基础与IOC依赖注入)
- spring学习总结一----控制反转与依赖注入
- 对spring依赖注入的一点小总结
- Spring依赖注入:注解注入总结
- Spring依赖注入:注解注入总结
- spring中依赖注入方式总结
- spirng项目搭建及spring依赖注入三种方式 温习总结
- Spring依赖注入学习总结
- spring学习总结(三):IOC & DI 配置 Bean 之配置形式及依赖注入方式