依赖注入和Guice理解

理解依赖注入,这篇文章写得非常好,结合spring的依赖注入分析的。
http://blog.csdn.net/taijianyu/article/details/2338311/
大体的意思是:
有一个类A,这个类里的属性m(属性是接口类型)需要我们通过构造器或者setter方法传入(比如传入一个B接口实现类的实例,为啥属性或方法的参数类型都是接口类型,因为这样可以传接口不同实现类的实例啊),来为这个类A的该属性m设值。类A有了m属性值,就可以实现很多逻辑了。
在spring框架里,我们不需要自己去向类A的构造器或者setter方法手动传参(甚至不用自己调用这个方法),我们把他们的依赖关系(类A和B接口的一个实现类)写在配置文件中,然后我们调用。
ApplicationContext ctx = new FileSystemXmlApplicationContext("bean.xml”);
A  a = (A)ctx.getBean(“A类在配置文件中的id名”);
这样我们生成的A的实例中属性m就已经是B接口的那个实现类的实例了。我们根本就不需要自己去手动传入B接口的那个实现类的实例。因为依赖关系已经在配置文件中生成了,那么我们生成实例时,会自动加载。并且因为我们是在配置文件中写的他们的依赖关系,所以如果我们想生成的A的实例的属性m的值是B接口的另外一个实现类实例,我们只需要稍微改下配置文件就可以了,不需要改动java代码。
 
再总结总结上面,就是说我们可以将一个接口类型绑定到一个特定的该接口实现类(这里更精确的说是该接口类型的参数值绑定到了某个特定的该接口实现类的实例上了)上。好处就是在某处代码中需要某个实例时,不用我们自己手动创建(甚至不用传入),而是实现写好了他们的依赖关系,要用的时候就会自动生成了,也就是可以说将某个接口实现类的实例已经注入到 某个类的某个变量中了,相偎相依。
 
guice也可以实现绑定,只是不再使用繁杂的配置文件,下面说说guice:
 
跟前面的例子类似:
  1. class RealBillingService implements BillingService {  
  2.   private final CreditCardProcessor processor;  
  3.   private final TransactionLog transactionLog;  
  4.   
  5.   @Inject  
  6.   RealBillingService(CreditCardProcessor processor,   
  7.       TransactionLog transactionLog) {  
  8.     this.processor = processor;  
  9.     this.transactionLog = transactionLog;  
  10.   }  
  11.   
  12.   @Override  
  13.   public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {  
  14.     ...  
  15.   }  
  16. }  
我们实现了BillingService接口(实现里面的chargeOrder()方法),我们在实现的过程中需要CreditCardProcessor和TransactionLog接口实现类的实例,那么我们怎么像spring 那样去给我们这两个CreditCardProcessor和TransactionLog接口类型的变量自动注入实例值呢?是这样的:
我们先自定义一个module,在configure()这个重写方法中进行绑定:
  1. public class BillingModule extends AbstractModule {  
  2.   @Override   
  3.   protected void configure() {  
  4.   
  5.      /* 
  6.       * This tells Guice that whenever it sees a dependency on a TransactionLog, 
  7.       * it should satisfy the dependency using a DatabaseTransactionLog. 
  8.       */  
  9.     bind(TransactionLog.class).to(DatabaseTransactionLog.class);  
  10.   
  11.      /* 
  12.       * Similarly, this binding tells Guice that when CreditCardProcessor is used in 
  13.       * a dependency, that should be satisfied with a PaypalCreditCardProcessor. 
  14.       */  
  15.     bind(CreditCardProcessor.class).to(PaypalCreditCardProcessor.class);  
  16.   }  
  17. }  
 
然后我们在需要使用BillingService接口实现类即RealBillingService实例的地方(我们需要RealBillingService实例中的CreditCardProcessor 接口变量 和  TransactionLog 接口变量都要初始化值,不然没法用,接下来就是做这个事情的,但不是手动直接传参),我们就利用我们创造的BillingModule来生成一个Injector实例:Injector injector = Guice.createInjector(new BillingModule());  
这个injector就具有的这样的功能,即看到标记了@Inject的方法或变量有TransactionLog类型(如果是标记的方法,那么就是方法的参数有这个类型),就弄一个DatabaseTransactionLog类的实例(这里的弄指的是比如,当外界需要获取TransactionLog时(比如一个方法(@Inject标记的方法只能是类的构造方法和setter()方法,不能是其他方法)的参数是TransactionLog类型,就是需要获取),就返回一个DatabaseTransactionLog对象引用(自动返回,不需要手动传参),如果是一个TransactionLog类型的成员变量标记了@inject,那么也是需要获取,那么也会自动给该变量赋值一个DatabaseTransactionLog对象引用
),看到CreditCardProcessor就弄成PaypalCreditCardProcessor。比如这里我们调用:injector.getInstance(RealBillingService.class); 因为RealBillingService java代码中有
  1.  @Inject  
  2.   RealBillingService(CreditCardProcessor processor,   
  3.       TransactionLog transactionLog) {  
  4.     this.processor = processor;  
  5.     this.transactionLog = transactionLog;  
  6.   }  
该方法有一个@inject,那么就会向方法参数中传入:如果是TransactionLog类型,就传入DatabaseTransactionLog这个类的实例。如果是CreditCardProcessor类型,就传入PaypalCreditCardProcessor类的实例。
 这样我们通过调用injector.getInstance(RealBillingService.class)得到的RealBillingService实例中的CreditCardProcessor 接口变量和TransactionLog接口变量已经初始化赋值了,和spring注入机制一样,这里赋值的过程也不需要我们手动的赋值,不需要我们调用构造方法进行传参,按上面的流程走一遍就自动赋值了。
 
上面这样写有一个问题,就是如果我们的RealBillingService这里面所有的标记了@Inject 的方法中和成员变量(假设方法参数里的参数类型和成员变量类型都是TransactionLog类型)需要的TransactionLog接口类型的实现类是不同的,我们上面就不能,因为上面我们绑定的时候,我们bind(TransactionLog.class).to(DatabaseTransactionLog.class); 那么injector看到@Inject下的含TransactionLog方法和变量时,就会传入和赋值DatabaseTransactionLog类型对象。所以这里并不能对于不同的TransactionLog
地方传入或赋值不同的TransactionLog实现类对象。那么我们怎么办呢?我们会使用@Named的方式,具体看代码,很容易理解:
  1. bind(CreditCardProcessor.class)  
  2.     .annotatedWith(Names.named("Checkout1"))  
  3.     .to(Checkout1CreditCardProcessor.class);  
  4. bind(CreditCardProcessor.class)  
  5.     .annotatedWith(Names.named("Checkout2"))  
  6.     .to(Checkout2CreditCardProcessor.class); 
 
 
  1. public class RealBillingService implements BillingService {  
  2.   
  3.   @Inject  
  4.   public RealBillingService(@Named("Checkout1") CreditCardProcessor processor,  
  5.       TransactionLog transactionLog) {  
  6.     ...  
  7.   }  
  8.  
  9.   
  10.  @Inject   
  11.  @Named("Checkout2”) 
  12. private CreditCardProcessor processor;  
  13. }
 
这样就可以了,相当于在不同的地方做了不同的标记。
 
除了在自己写的module里面的configure()中进行bind()绑定,我们也可以不再这里面绑定,而是在一个接口或父类中直接进行声明。如果用injector对一个java类中的标记了@Inject 的变量和方法进行转换的时候(injector和@inject都不能少,一个是需要injector = Guice.createInjector(new BillingModule()),虽然这时候BillingModule()可以是空,但是还是需要Guice这个角色进行“转换”,因为Guice起着创造(bindBtoA)类B的对象的作用。并且需要@Inject 进行标记包含想要转换的变量的成员变量和方法),如果这个需要转换的类型在创建的时候已经写了如:
@ImplementedBy (SayHello.class)
  1. public interface Talk {  
  2.     public void sayHello();  
  3. }  
那么这样就算绑定了,其实和前面的链式bind()那种方式绑定一样的功能,只是绑定的位置不一样。但和链式绑定不同的是它们的优先级,@ImplementedBy实现的是一种default绑定,当同时存在@ImplementedBy和链式绑定时,链式绑定起作用。
 
provider的注入需要的时候再看。

 

posted @ 2015-09-07 16:37  happy_lion  阅读(1187)  评论(0编辑  收藏  举报