spring的主要内容之依赖注入
---------------------------------------------------依赖注入的含义------------------
在上一篇中提到过,spring的主要目的是简化开发和松散耦合。依赖注入技术就是为了松散耦合而出现的。
在旧的编程观念中,A类如果需要B类的实例,就需要通过new关键字新建一个B类的实例。这种编程方式需要依赖类确定被依赖类的具体实现类。
如果使用spring的依赖注入技术,则依赖类只需要声明自己需要某个类的实例,而不需要确定被依赖类的具体实现类,具体的实现类由spring框架通过配置文件来确定。
使用spring的依赖注入技术可以松散耦合。当需要更换被依赖类的具体实现类时,只需要创建一个新的实现类,然后把spring配置文件中之前配置的实现类改为新的实现类即可,而不需要对依赖类做改动。
由此也可以看出,spring的依赖注入(DI)实际上是把被依赖类的具体实现类的确定权交给了spring框架,而不再是由依赖类来确定。因此,依赖注入也被称为控制反转。
---------------------------------------------------依赖注入发生的时机------------------
那么依赖注入发生在什么时候呢?是发生在项目启动的过程中的。
当我们启动项目时,spring会进行容器的初始化,spring会扫描整个项目,如果发现spring的xml配置文件、spring的注入注解、基于java的配置注解,就会实例化相关的类,这些类包括controller、service、dao等。这个过程中包含依赖注入。实例化得到的Bean将由spring容器管理。
我以前一直以为当页面发生事件请求controller时,spring才去实例化对应的controller类。现在看来,我之前的想法是错误的。controller类早在项目启动的过程中,就已经实例化了。当事件请求controller时,spring容器会在容器中找到对应controller的一个实例,然后执行对应的方法。
---------------------------------------------------依赖注入的步骤和方式------------------
总的看来,依赖注入的实现实际上分为三个步骤。
第一是解析spring配置文件中配置的bean和java代码中的注解的bean。
第二是实例化配置的所有bean。
实例化的途径是调用合适的构造方法。什么叫合适的构造方法呢?我们知道一个类可以有多个构造方法,如果有多个构造方法,spring容器会根据给定的参数个数和参数类型去寻找符合条件的构造方法并调用。如果只有一个构造方法,spring容器会直接调用这个构造方法,若参数列表不能够匹配,则会报错。
第三是注入依赖的bean。
注入方式共有三种,包括自动装配、java配置注入、XML注入。
---------------------------------------------------分界线------------------
第一,自动装配。
自动装配通过注解扫描实现。spring会根据依赖类的要求,自动从容器中取得符合要求的实例并注入给依赖类。
在java代码中注解bean,会用到spring的三个注解,分别是@ComponetScan、@Component和@Autowired。
@ComponentScan表示被注解的类所在包及其子包会被列入扫描范围。如果想把这个配置类和应用代码分开,则可以在一个单独的包中建立这个配置类,在这个类上使用@ComponentScan(basePackages="packageName")或者@ComponentScan(basePackages={"packageName1", "packageName2"})来指定扫描范围。为了代码的友好性,也可以使用@ComponentScan(basePackageClasses={A.class, B.class}),其含义为将这些类所在包及其子包列入扫描范围。
--->>>如果是使用配置文件来开启组件扫描,可以使用:<context:componet-scan base-package=""/>来指定。有人可能要问,既然可以使用注解配置,为什么还需要配置文件呢?因为如果依赖的是第三方工具类,我们是无法在第三方工具类上加@Component注解的,所以只能通过配置文件添加依赖了,配置文件只需指定第三方工具类的路径即可。
@Component是用来注解类的,用@Component注解一个类,就告诉了spring要在容器中创建这个类的实例并管理它,此时创建的实例的id为小写首字母的类名。如果要指定特定的实例名称,可以使用@Component("beanName")。
其实,@Component是通用的注解,一般我们为了提高可读性,会使用@Controller、@Service、@Repository等具体的注解。在项目启动过程中,spring容器会进行初始化。初始化时如果发现@Controller注解,就会实例化这个controller类。其他注解也是如此。
@Autowired是用来注解属性、构造方法、set方法、或一般带参方法的,告诉spring为这个属性或方法注入一个符合要求的实例。
---------------------------------------------------分界线------------------
第二,java配置注入。
package com.zaoren.configuration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.zaoren.controller.UserController; import com.zaoren.service.UserService; @Configuration public class BeanConfiguration { @Bean(name="uService") public UserService userService() { System.out.println("create userService"); return new UserService(); } @Bean public UserController userController(UserService uService) { System.out.println("create userController"); UserController uController = new UserController(uService); return uController; } } ---------------------------------------------------------------------- import org.springframework.web.bind.annotation.RequestMethod; import com.zaoren.service.UserService; @RequestMapping(value="user") public class UserController { private UserService uService; @RequestMapping(value="buy",method=RequestMethod.GET) public void buy() { System.out.println("--------->>>buy a book."); uService.searchBook(); } public UserController(UserService uService){ this.uService = uService; } }
spring初始化容器时会扫描项目文件,当发现类被@Configuration注解时,就知道这个类是用来装配实例的,然后才会扫描具体的方法并创建实例。如果没有@Configuration注解,则@Bean注解将不会起作用,即不会执行此文件中创建实例的方法。
@Bean注解告诉了spring框架,这个方法要返回一个bean,要求spring框架按照这个方法的逻辑创建实例,并纳入spring上下文中进行管理。
其实@Configuration有些类似于xml配置方式中的<beans>标签,@Bean类似于xml配置方式中的<bean>标签。
通过@Bean创建的实例的id默认是方法名(此处即aClass)。如果想要自定义实例id,可以这样指定id:@Bean(name=“myBean”)。
---------------------------------------------------分界线------------------
第三,XML配置文件注入。
XML配置文件注入是最原始的装配方式。配置文件以<beans>标签为根节点,使用<bean>标签声明具体的实例,其注入对象的方式有两种,一种是使用标签<constructor-arg>进行构造方法注入,另一种是使用<property>进行set方法注入。
注入的依赖对象的类型又可以分为三种,第一种是注入字面值,第二种是注入对象,第三种是注入集合。注入字面值要使用<constructor-arg>/<property>标签的value属性,注入对象引用要使用<constructor-arg>/<property>标签的ref属性,注入集合要使用<constructor-arg>/<property>标签的子标签<list>/<set>。
其中注入集合又分为两种,一种是字面值集合,另一种是对象集合。注入字面值集合要使用标签<list>/<set>的子标签<value>,注入对象集合要使用标签<list>/<set>的子标签<ref>。
其实这里的<beans>标签类似于java配置注入方式中的@Configuration注解,<bean>注解类似于java配置注入方式中的@Bean注解。具体代码如下:
package com.zaoren.controller; import java.util.List; import java.util.Set; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import com.zaoren.bean.BookFriend; import com.zaoren.service.BossService; import com.zaoren.service.UserService; @RequestMapping(value="user") public class UserController { private UserService uService; private BossService bService; private String bookName; private List<String> bookNameList; private Set<String> bookNoSet; private List<BookFriend> bookFriends; @RequestMapping(value="trade",method=RequestMethod.GET) public void trade() { System.out.println("--------->>>buy a book."); System.out.println("The name of the book is"+ bookName); System.out.println("查看待购清单,发现还有如下书籍:"); for(String bookName : bookNameList) { System.out.println(bookName); } System.out.println("查看待购清单,发现还有如下书籍编号:"); for(String bookNo : bookNoSet) { System.out.println(bookNo); } System.out.println("有书友如下:"); for(BookFriend bookFriend : bookFriends) { System.out.println(bookFriend); } uService.searchBook(); bService.downBook(); } public void setuService(UserService uService) { System.out.println("调用setuService"); this.uService = uService; } public void setbService(BossService bService) { System.out.println("调用setbService"); this.bService = bService; } public void setBookName(String bookName) { System.out.println("调用setBookName"); this.bookName = bookName; } public void setBookNameList(List<String> bookNameList) { System.out.println("调用setBookNameList"); this.bookNameList = bookNameList; } public void setBookNoSet(Set<String> bookNoSet) { System.out.println("调用setBookNoSet"); this.bookNoSet = bookNoSet; } public void setBookFriends(List<BookFriend> bookFriends) { System.out.println("调用setBookFriends"); this.bookFriends = bookFriends; } public UserController() { System.out.println("调用无参构造方法"); } public UserController(UserService uService,BossService bService,String bookName, List<String> bookNameList, Set<String> bookNoSet, List<BookFriend>bookFriends ){ System.out.println("六参构造方法调用"); this.uService= uService; this.bService = bService; this.bookName = bookName; this.bookNameList = bookNameList; this.bookNoSet = bookNoSet; this.bookFriends = bookFriends; } }
第一种注入方式,使用标签<constructor-arg>进行构造方法注入:
<bean id="uService" class="com.zaoren.service.UserService"/> <bean id="bService" class="com.zaoren.service.BossService"/> <bean id="bookFriend" class="com.zaoren.bean.BookFriend"/> <bean id="uController" class="com.zaoren.controller.UserController"> <constructor-arg ref="uService"/> <constructor-arg ref="bService"/> <constructor-arg value="鲁宾逊漂流记"/> <constructor-arg> <list> <value>茶花女</value> <value>三重门</value> <value>明朝那些事儿</value> </list> </constructor-arg> <constructor-arg> <set> <value>111222</value> <value>111223</value> <value>111224</value> <value>111224</value> </set> </constructor-arg> <constructor-arg> <list> <ref bean="bookFriend"/> <ref bean="bookFriend"/> <ref bean="bookFriend"/> </list> </constructor-arg> </bean>
第二种注入方式,使用标签<property>进行set方法注入:
<bean id="uController" class="com.zaoren.controller.UserController"> <property name="uService" ref="uService"/> <property name="bService" ref="bService"/> <property name="bookName" value="鲁宾逊漂流记2"/> <property name="bookNameList"> <list> <value>茶花女</value> <value>三重门</value> <value>明朝那些事儿</value> </list> </property> <property name="bookNoSet"> <set> <value>111222</value> <value>111223</value> <value>111224</value> <value>111224</value> </set> </property> <property name="bookFriends"> <list> <ref bean="bookFriend"/> <ref bean="bookFriend"/> <ref bean="bookFriend"/> </list> </property> </bean>
运行结果为:
上面代码中的"鲁宾逊漂流记"则属于字面值注入,uService和bService属于对象引用注入,“茶花女“、“111222”、bookFriend属于集合注入。
配置文件的构造参数顺序要和构造方法的构造参数的顺序相同。
需要注意的是,字面值都是以字符串的类型注入的,不会以其他类型注入。上面的111222就是一个字符串,而非数字。
--------------------------------------------------------------
由上可知,spring的创建实例的依据有两种,一种是配置文件,一种是注解。
配置文件中的依据又分为两种,一种是构造方法注入,另一种是setter方法注入。
注解的依据又分为两种,一种是@Component(@Controller,@Service,@Repository),另一种是@Configuration。