简介
在前面的文章里,我已经对依赖注入的基本概念做了一个介绍。我们已经知道了依赖注入的意义和目的。但是在牵涉到具体实现的时候,我们有好几种不同的选择,其中就有自动关联(autowire),java代码关联(java config)以及传统的xml文件配置关联。本文针对这几种形式结合具体的示例做一个讨论和总结。
问题场景介绍
假设我们有如下的一个问题,在下图中,我们有一个Service接口,定义特定的业务逻辑。在一个具体的实现中,BusinessService需要使用到第三方的数据,这里用接口DAO表示。为了保证松耦合和方便测试,有一个典型的实现BusinessDAO。
当然,因为根据我们具体情况的需要,我们完全可以定义其他的实现。现在的问题是,如果需要采用依赖注入的方式让上述的BusinessService, BusinessDAO一起工作,同时在我们的业务代码里又没有对它们的耦合,具体该有几种详细的实现呢?具体实现的细节改如何呢?我们就一一看过来。
XML配置文件关联
从最传统的这种方式开始。在上文中我们也已经举出过几个示例。一般我们将一个简单java对象定义为一个bean。而我们常用代码里对象之间的依赖关系通过构造函数或者属性方法的方式注入设置。在这种方式里,每个java对象就是一个单独定义的单元,从它们自身的定义里更多的情况下它们对于外界的依赖是一个抽象的接口或者抽象基类。而通过我们定义的关联关系,使得它们在最终被使用和运行的时候,各种具体的依赖关系已经构造好了。
现在需要的就是按照前面图中定义的关系来构造示例。首先定义的Service接口如下:
package com.yunzero; public interface Service { void doBusiness(); }
对应的一个实现BusinessService如下:
package com.yunzero.impl; import com.yunzero.DAO; import com.yunzero.Service; public class BusinessService implements Service { private DAO dao; public void setDao(DAO dao) { this.dao = dao; } @Override public void doBusiness() { System.out.println("Business impl in business service."); System.out.println(dao.getId()); } }
这部分的代码其实就是一个通过属性注入的方式产生了一个对抽象接口DAO的依赖。而DAO的实现如下:
package com.yunzero; public interface DAO { int getId(); }
现在需要再定义的就是DAO的一个实现BusinessDAO:
package com.yunzero.impl; import com.yunzero.DAO; public class BusinessDAO implements DAO { @Override public int getId() { return 0; } }
有了这几步实现之后,剩下的就是将它们给拼在一起形成一个完整的应用。采用xml关联的方式该怎么来实现呢?我们先定义一个bean配置文件sampleContext.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"> <bean id="service" class="com.yunzero.impl.BusinessService"> <property name="dao" ref="dao"/> </bean> <bean id="dao" class="com.yunzero.impl.BusinessDAO"/> </beans>
我们来看它详细的定义。首先最外层的是<beans> 节点,然后有一个定义id为service的bean。它对应的具体实现是BusinessService。留意到前面代码里BusinessService对DAO接口的属性设置依赖。那里定义了一个setDAO的方法。在这里可以通过设置<property>属性将真正的实现关联进来。在这里是ref引用的dao。而id为dao的bean则是BusinessDAO的具体实例。这样,通过这么一个定义各个需要的bean以及它们之间的依赖,它们的依赖注入配置基础就弄好了。在实际代码中要构造出这些定义好的对象则比较简单,一个使用它们的代码实例如下:
package com.yunzero; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class App { public static void main( String[] args ) { AbstractApplicationContext ctx = new ClassPathXmlApplicationContext("sampleContext.xml"); Service service = ctx.getBean(Service.class); service.doBusiness(); ctx.close(); } }
上述代码里使用ApplicationContext来获取对象定义的容器。这里获取Service对象采用的是ctx.getBean(class)的方式。以前也有直接获取bean名字,然后再做一个强制类型转换的方式。和那种比起来,这种方式的代码更加简洁一些。
如果这个时候运行代码,输出的结果如下:
Mar 30, 2015 10:02:21 PM org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@7106e68e: startup date [Mon Mar 30 22:02:21 CST 2015]; root of context hierarchy Mar 30, 2015 10:02:21 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions INFO: Loading XML bean definitions from class path resource [sampleContext.xml] Business impl in business service. 0 Mar 30, 2015 10:02:22 PM org.springframework.context.support.ClassPathXmlApplicationContext doClose INFO: Closing org.springframework.context.support.ClassPathXmlApplicationContext@7106e68e: startup date [Mon Mar 30 22:02:21 CST 2015]; root of context hierarchy
完整实现的详细代码会放在后面的附件里。
上述的这种方式可以说是最常用办法之一。它的特点在于非常的简单直观。任何需要定义以及依赖的bean对象都会通过这种方式定义出来。当然,它也有一些不足的地方。如果我们后续的代码作一些变动,这里依赖定义的都是字符串,比较容易出错,而且有时候代码改动之后还要改配置,整个过程显得比较繁琐。
自动关联
正因为有上述的问题,spring引入了另外一种注入的方式。这种方式相对来说更加简单一些。可以说实现了零配置。以前面的示例为基础,我们定义的接口还是没有任何变化,但是具体针对接口的实现。它们稍微有一点变化。比如BusinessService和BusinessDAO的实现分别如下:
package com.yunzero.impl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.yunzero.DAO; import com.yunzero.Service; @Component public class BusinessService implements Service { private DAO dao; @Autowired public void setDao(DAO dao) { this.dao = dao; } @Override public void doBusiness() { System.out.println("Business impl in business service."); System.out.println(dao.getId()); } }
package com.yunzero.impl; import org.springframework.stereotype.Component; import com.yunzero.DAO; @Component public class BusinessDAO implements DAO { @Override public int getId() { return 0; } }
这些代码和前面的实现基本上一样,除了一个地方,就是这两个实现的类定义的地方多了一个@Component的标注。这就相当于告诉spring这是一个已经定义好的bean。我们也可以在@Component里面增加对bean的自定义信息。这样,对于一个定义的抽象接口,@Component相当于标注这个类就是对应该接口的一个注入实现。那么,对于那些还依赖其他bean的情况呢?
在前面的代码里有一个DAO的依赖,而且在对dao元素的setDAO()方法里有一个标注@Autowired,这就相当于将一个DAO的实现和BusinessDAO关联起来了。而怎么找这个DAO的具体实现呢?和前面的一样,有一个BusinessDAO的实现并且它也被标注为@Component。
有了这些bean和它们之间关联的定义了,spring里该怎么使用它们呢?虽然我们定义了这些component以及它们之间的关联关系,但是spring并不是那么聪明的就知道。它需要去扫描所有这些类才知道哪些类是哪些接口的实现以及关联了哪些类。所以和前面的xml文件配置类似,它需要一个指定配置信息的地方。为了省略硬性的xml文件配置,可以定义一个专门用来扫描的类,比如这里我们可以定义一个类ServiceConfig:
package com.yunzero.config; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan(basePackages="com.yunzero") public class ServiceConfig { }
它的作用相当于替换了前面的配置文件。这个类其实本身并不实现任何特殊的业务逻辑。仅仅相当于起到一个类似于配置文件的作用。在实际项目中应当尽量将这类文件放在一个单独的包里。另外,在ServiceConfig的实现里,它有一个@ComponentScan的标注。这个标注用来指示spring扫描包的目录。一般来说spring默认扫描该类所在的包以及下面所包含的子包。这里采用自定义指定包路径的方式。
经过这些修改,我们使用自动配置的运行代码如下:
package com.yunzero; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.support.AbstractApplicationContext; import com.yunzero.config.ServiceConfig; public class App { public static void main( String[] args ) { AbstractApplicationContext ctx = new AnnotationConfigApplicationContext(ServiceConfig.class); Service service = ctx.getBean(Service.class); service.doBusiness(); ctx.close(); } }
采用这种方法的要点在于将配置类ServiceConfig.class作为参数提供给AnnotationConfigApplicationContext。其他的使用方式则基本上一样。
这样,采用自动化配置的方式就完成了。总的来说,这种方式就是将替换抽象接口的bean采用@Component标注好,然后将抽象依赖之间的关联用@Autowired标注好。最后用一个配置类告诉spring去哪些包从上往下的去扫描。
Java配置代码关联
除了上述的两种方法以外,还要一种办法就是配置代码关联。采用自动化配置的方法固然好,它可以做到不用写任何配置文件,但是也有一个问题。就是如果我们需要引用某些第三方的类库时,由于那些库的源代码并不在我们的掌握之中,它们并没有作那些@Component标注,这个时候再采用原来的办法就不可行了。如果我们希望能够解决这个问题的同时还能够尽可能少的使用配置文件,可以借鉴一部分前面ServiceConfig类的方法。我们这里定义一个如下的SampleConfig类:
package com.yunzero.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.yunzero.DAO; import com.yunzero.Service; import com.yunzero.impl.BusinessDAO; import com.yunzero.impl.BusinessService; @Configuration public class SampleConfig { @Bean public Service businessService() { BusinessService service = new BusinessService(); service.setDao(businessDAO()); return new BusinessService(); } @Bean public DAO businessDAO() { return new BusinessDAO(); } }
注意到,这里这个类的定义没有添加@ComponentScan标注。这里只是针对每个Bean的创建采用专门@Bean修饰的方法单独拎了出来。这里的方法相当于定义了一系列的工厂方法。对于每个bean的创建就通过返回对应的对象。@Bean在这里起到一个标注和定义bean的作用,而且创建的这个bean的id和定义的方法名相同。
具体对这种情况的使用和前面的一样,这里就不再赘述。
总结
采用xml配置文件、自动配置和java 配置类是最主要的几种注入方法。通常来说,采用自动配置的方式最简单省事,一方面因为它只需要在代码里添加一点标注,而且不需要去定义任何配置文件,另外,因为这些标注是和代码关联的,它具有强类型相关性,能够更快的检测的错误,这样可以避免在配置文件中产生的错误。 当然,从个人的角度来说,采用xml文件的方式可以实现一个更加理想的配置和代码分离,保证代码编译之后基本上不需要做任何改动,可以做到代码和配置的完全隔离。
参考材料
http://www.amazon.com/Spring-Action-Craig-Walls/dp/161729120X/ref=sr_1_1?s=books&ie=UTF8&qid=1427984830&sr=1-1&keywords=spring+in+action+5th+edition
相关推荐
下面介绍几种常用的依赖注入相关的注解: - **@Component**:用于标记一个普通的Java类为Spring管理的Bean。 - **@Service**:通常用于标记业务层的组件。 - **@Repository**:用于标记数据访问层(DAO层)的组件。...
Spring 依赖注入的几种方式 依赖注入(Dependency Injection,简称 DI)是一种设计模式,它可以将对象之间的耦合关系降到最低,从而提高系统的灵活性和可维护性。在 Spring 框架中,依赖注入是通过 IoC 容器来实现...
除了以上三种方式,Spring还提供了基于注解的元数据注入,如`@Resource`、`@Qualifier`等,以及XML配置文件中的`<bean>`标签等方式进行依赖注入。在实际开发中,可以根据需求选择合适的方式,通常推荐使用构造器注入...
Spring Boot 中的几种注入方法 在 Spring Boot 中,注入是一种非常重要的机制,用于将 bean 对象注入到其他 bean 对象中,以便实现松耦合和高内聚的设计目标。下面我们将对 Spring Boot 中的几种注入方法进行详细的...
在Spring中,依赖注入主要通过以下几种方式实现: 1. **构造器注入**:通过在类的构造函数中传递依赖对象的实例,Spring容器会在创建目标对象时调用合适的构造函数并注入依赖。 2. **setter注入**:在类中定义...
本篇学习笔记将深入剖析Spring依赖注入的原理,通过源码分析帮助我们理解这一核心机制。 首先,依赖注入允许我们解耦组件之间的关系,使得各个组件可以独立地进行开发、测试和维护。在Spring中,DI主要通过两种方式...
在Spring框架中,管理Bean的方式主要有三种:XML配置、注解配置和Java配置。下面将详细介绍这三种方式以及Spring的自动注入机制。 1. **基于XML的Bean定义**: 在XML配置中,我们通常在`applicationContext.xml`...
在Spring中,依赖注入可以通过XML配置文件实现,本文将重点介绍两种常见的注入方式:Set注入和构造器注入。 1. Set注入 Set注入是最常见的注入方式,适用于对象没有默认构造函数或构造函数不接受参数的情况。首先,...
在深入探讨Spring框架中基于注解(Annotation)的依赖注入(Dependency Injection,简称DI)实现之前,我们首先需要理解几个核心概念:Spring框架、依赖注入、以及注解本身。 ### Spring框架简介 Spring框架是一个...
Spring框架是Java开发中不可或缺的一部分,它以IoC(Inversion of Control,控制反转)和DI(Dependency Injection,依赖注入)为核心理念,极大地简化了组件之间的耦合,提高了代码的可测试性和可维护性。...
Spring中的依赖注入主要通过以下三种方式实现: 1. 构造器注入:通过构造函数传递依赖对象,Spring容器在创建目标对象时会调用合适的构造器,并传入相应的依赖。 2. setter方法注入:通过setter方法注入依赖对象,...
### Spring依赖注入详解 #### 一、什么是IoC与DI? 在探讨Spring框架中的依赖注入之前,我们首先需要了解两个核心概念:IoC(Inversion of Control)与DI(Dependency Injection)。这两个概念是理解Spring框架...
依赖注入是一种设计模式,它允许我们解耦组件,提高代码的可测试性和可维护性。Spring框架通过IoC容器来实现DI,让我们无需手动创建对象,而是由框架来管理这些对象及其依赖关系。现在,我们将深入探讨如何通过自己...
依赖注入可以根据依赖传递的方式分为以下几种类型: - **Setter注入**:通过setter方法来注入依赖。 - **构造函数注入**:通过构造函数参数来注入依赖。 - **依赖获取**:对象主动请求依赖项。 ##### 3.1.1 Setter...
在Spring框架中,依赖注入(Dependency Injection,简称DI)是一种重要的设计模式,它允许我们创建松耦合的组件,使得代码更加灵活、可测试和易于维护。本系列文章聚焦于Spring与IoC(Inversion of Control,控制...
Spring框架最初是为了简化企业应用开发而设计的,它提供了一种轻量级的方式来管理依赖注入(Dependency Injection,DI)和面向切面编程(Aspect-Oriented Programming,AOP)。Spring的核心特性包括但不限于: 1. *...
Java 开发学习(六)----DI 依赖注入之 setter 及构造器注入解析 本文将详细介绍 Java 中的依赖注入(Dependency Injection,简称 DI),特别是 setter 注入和构造器注入两种方式的使用和实现。 一、DI 依赖注入 ...