3.3.4  依赖检查

       上一节介绍的自动装配,很可能发生没有匹配的Bean进行自动装配,如果此种情况发生,只有在程序运行过程中发生了空指针异常才能发现错误,如果能提前发现该多好啊,这就是依赖检查的作用。


依赖检查:用于检查Bean定义的属性都注入数据了,不管是自动装配的还是配置方式注入的都能检查,如果没有注入数据将报错,从而提前发现注入错误,只检查具有setter方法的属性。

Spring3+也不推荐配置方式依赖检查了,建议采用Java5+ @Required注解方式,测试时请将XML schema降低为2.5版本的,和自动装配中“autodetect”配置方式的xsd一样。


java代码:
  1. <?xml version="1.0" encoding="UTF-8"?>  

  2. <beans  xmlns="http://www.springframework.org/schema/beans"

  3.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  4.        xsi:schemaLocation="  

  5.           http://www.springframework.org/schema/beans

  6.           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd

  7. </beans>  


依赖检查有none、simple、object、all四种方式,接下来让我们详细介绍一下:


一、none默认方式,表示不检查;


二、objects检查除基本类型外的依赖对象,配置方式为:dependency-check="objects",此处我们为HelloApiDecorator添加一个String类型属性“message”,来测试如果有简单数据类型的属性为null,也不报错;


java代码:
  1. <bean id="helloApi"class="cn.javass.spring.chapter2.helloworld.HelloImpl"/>  

  2. <!-- 注意我们没有注入helloApi,所以测试时会报错 -->  

  3. <bean id="bean"

  4. class="cn.javass.spring.chapter3.bean.HelloApiDecorator"

  5.     dependency-check="objects">  

  6. <property name="message" value="Haha"/>  

  7. </bean>  


      注意由于我们没有注入bean需要的依赖“helloApi”,所以应该抛出异常UnsatisfiedDependencyException,表示没有发现满足的依赖:


java代码:
  1. package cn.javass.spring.chapter3;  

  2. import java.io.IOException;  

  3. import org.junit.Test;  

  4. import org.springframework.beans.factory.UnsatisfiedDependencyException;  

  5. import org.springframework.context.support.ClassPathXmlApplicationContext;  

  6. publicclass DependencyCheckTest {  

  7. @Test(expected = UnsatisfiedDependencyException.class)  

  8. publicvoid testDependencyCheckByObject() throws IOException {  

  9. //将抛出异常

  10. new ClassPathXmlApplicationContext("chapter3/dependency-check-object.xml");  

  11.    }  

  12. }  


三、simple对基本类型进行依赖检查,包括数组类型,其他依赖不报错;配置方式为:dependency-check="simple",以下配置中没有注入message属性,所以会抛出异常:


java代码:
  1. <bean id="helloApi"class="cn.javass.spring.chapter2.helloworld.HelloImpl"/>  

  2. <!-- 注意我们没有注入message属性,所以测试时会报错 -->  

  3. <bean id="bean"

  4. class="cn.javass.spring.chapter3.bean.HelloApiDecorator"

  5.     dependency-check="simple">  

  6.   <property name="helloApi" ref="helloApi"/>  

  7. </bean>  


四、all:对所以类型进行依赖检查,配置方式为:dependency-check="all",如下配置方式中如果两个属性其中一个没配置将报错。


java代码:
  1. <bean id="helloApi"class="cn.javass.spring.chapter2.helloworld.HelloImpl"/>  

  2. <bean id="bean"

  3. class="cn.javass.spring.chapter3.bean.HelloApiDecorator"

  4.     dependency-check="all">  

  5.  <property name="helloApi" ref="helloApi"/>  

  6. <property name="message" value="Haha"/>  

  7. </bean>  


依赖检查也可以通过“<beans>”标签中default-dependency-check属性来指定全局依赖检查配置。

3.3.5 方法注入

所谓方法注入其实就是通过配置方式覆盖或拦截指定的方法,通常通过代理模式实现。Spring提供两种方法注入:查找方法注入和方法替换注入。

因为Spring是通过CGLIB动态代理方式实现方法注入,也就是通过动态修改类的字节码来实现的,本质就是生成需方法注入的类的子类方式实现。

在进行测试之前,我们需要确保将“com.springsource.cn.sf.cglib-2.2.0.jar”放到lib里并添加到“Java Build Path”中的Libararies中。否则报错,异常中包含nested exception is java.lang.NoClassDefFoundError: cn/sf/cglib/proxy/CallbackFilter

9e283ca4d198d742a3b197e4e823d96f__1.JPG

      传统方式和Spring容器管理方式唯一不同的是不需要我们手动生成子类,而是通过配置方式来实现;其中如果要替换createPrinter()方法的返回值就使用查找方法注入;如果想完全替换sayHello()方法体就使用方法替换注入。       接下来让我们看看具体实现吧。


一、查找方法注入:又称为Lookup方法注入,用于注入方法返回结果,也就是说能通过配置方式替换方法返回结果。使用<lookup-method name="方法名" bean="bean名字"/>配置;其中name属性指定方法名,bean属性指定方法需返回的Bean。

方法定义格式:访问级别必须是public或protected,保证能被子类重载,可以是抽象方法,必须有返回值,必须是无参数方法,查找方法的类和被重载的方法必须为非final:

<public|protected> [abstract] <return-type> theMethodName(no-arguments);


因为“singleton”Bean在容器中只有一个实例,而“prototype”Bean是每次获取容器都返回一个全新的实例,所以如果“singleton”Bean在使用“prototype” Bean情况时,那么“prototype”Bean由于是“singleton”Bean的一个字段属性,所以获取的这个“prototype”Bean就和它所在的“singleton”Bean具有同样的生命周期,所以不是我们所期待的结果。因此查找方法注入就是用于解决这个问题。


1)  首先定义我们需要的类,Printer类是一个有状态的类,counter字段记录访问次数:


java代码:
  1. package cn.javass.spring.chapter3.bean;  

  2. publicclass Printer {  

  3. privateint counter = 0;  

  4. publicvoid print(String type) {  

  5.        System.out.println(type + " printer: " + counter++);  

  6.    }  

  7. }  


      HelloImpl5类用于打印欢迎信息,其中包括setter注入和方法注入,此处特别需要注意的是该类是抽象的,充分说明了需要容器对其进行子类化处理,还定义了一个抽象方法createPrototypePrinter用于创建“prototype”Bean,createSingletonPrinter方法用于创建“singleton”Bean,此处注意方法会被Spring拦截,不会执行方法体代码:


java代码:
  1. package cn.javass.spring.chapter3;  

  2. import cn.javass.spring.chapter2.helloworld.HelloApi;  

  3. import cn.javass.spring.chapter3.bean.Printer;  

  4. publicabstractclass HelloImpl5 implements HelloApi {  

  5. private Printer printer;  

  6. publicvoid sayHello() {  

  7.        printer.print("setter");  

  8.        createPrototypePrinter().print("prototype");  

  9.        createSingletonPrinter().print("singleton");

  10.    }  

  11. publicabstract Printer createPrototypePrinter();  

  12. public Printer createSingletonPrinter() {  

  13.        System.out.println("该方法不会被执行,如果输出就错了");  

  14. returnnew Printer();  

  15.    }  

  16. publicvoid setPrinter(Printer printer) {  

  17. this.printer = printer;  

  18.    }  

  19. }  


2)  开始配置了,配置文件在(resources/chapter3/lookupMethodInject.xml),其中“prototypePrinter”是“prototype”Printer,“singletonPrinter”是“singleton”Printer,“helloApi1”是“singleton”Bean,而“helloApi2”注入了“prototype”Bean:


java代码:
  1. <bean id="prototypePrinter"

  2. class="cn.javass.spring.chapter3.bean.Printer" scope="prototype"/>  

  3. <bean id="singletonPrinter"

  4. class="cn.javass.spring.chapter3.bean.Printer" scope="singleton"/>  

  5. <bean id="helloApi1"class="cn.javass.spring.chapter3.HelloImpl5" scope="singleton">  

  6. <property name="printer" ref="prototypePrinter"/>  

  7. <lookup-method name="createPrototypePrinter" bean="prototypePrinter"/>  

  8. <lookup-method name="createSingletonPrinter" bean="singletonPrinter"/>  

  9. </bean>            

  10. <bean id="helloApi2"class="cn.javass.spring.chapter3.HelloImpl5" scope="prototype">  

  11. <property name="printer" ref="prototypePrinter"/>  

  12. <lookup-method name="createPrototypePrinter" bean="prototypePrinter"/>  

  13. <lookup-method name="createSingletonPrinter" bean="singletonPrinter"/>  

  14. </bean>            


      3)测试代码如下:


java代码:
  1. package cn.javass.spring.chapter3;  

  2. import org.junit.Test;  

  3. import org.springframework.context.support.ClassPathXmlApplicationContext;  

  4. import cn.javass.spring.chapter2.helloworld.HelloApi;  

  5. publicclass MethodInjectTest {  

  6. @Test

  7. publicvoid testLookup() {  

  8. ClassPathXmlApplicationContext context =  

  9. new ClassPathXmlApplicationContext("chapter3/lookupMethodInject.xml");  

  10.        System.out.println("=======singleton sayHello======");  

  11.        HelloApi helloApi1 = context.getBean("helloApi1", HelloApi.class);  

  12.        helloApi1.sayHello();  

  13.        helloApi1 = context.getBean("helloApi1", HelloApi.class);  

  14.        helloApi1.sayHello();  

  15.        System.out.println("=======prototype sayHello======");  

  16.        HelloApi helloApi2 = context.getBean("helloApi2", HelloApi.class);  

  17.        helloApi2.sayHello();  

  18.        helloApi2 = context.getBean("helloApi2", HelloApi.class);  

  19.        helloApi2.sayHello();  

  20. }}  


      其中“helloApi1”测试中,其输出结果如下:


java代码:
  1. =======singleton sayHello======  

  2. setter printer: 0

  3. prototype printer: 0

  4. singleton printer: 0

  5. setter printer: 1

  6. prototype printer: 0

  7. singleton printer: 1


      首先“helloApi1”是“singleton”,通过setter注入的“printer”是“prototypePrinter”,所以它应该输出“setter printer:0”和“setter printer:1”;而“createPrototypePrinter”方法注入了“prototypePrinter”,所以应该输出两次“prototype printer:0”;而“createSingletonPrinter”注入了“singletonPrinter”,所以应该输出“singleton printer:0”和“singleton printer:1”。

      而“helloApi2”测试中,其输出结果如下:


java代码:
  1. =======prototype sayHello======  

  2. setter printer: 0

  3. prototype printer: 0

  4. singleton printer: 2

  5. setter printer: 0

  6. prototype printer: 0

  7. singleton printer: 3


      首先“helloApi2”是“prototype”,通过setter注入的“printer”是“prototypePrinter”,所以它应该输出两次“setter printer:0”;而“createPrototypePrinter”方法注入了“prototypePrinter”,所以应该输出两次“prototype printer:0”;而“createSingletonPrinter”注入了“singletonPrinter”,所以应该输出“singleton printer:2”和“singleton printer:3”。

      大家是否注意到“createSingletonPrinter”方法应该输出“该方法不会被执行,如果输出就错了”,而实际是没输出的,这说明Spring拦截了该方法并使用注入的Bean替换了返回结果。

方法注入主要用于处理“singleton”作用域的Bean需要其他作用域的Bean时,采用Spring查找方法注入方式无需修改任何代码即能获取需要的其他作用域的Bean。


二、替换方法注入:也叫“MethodReplacer”注入,和查找注入方法不一样的是,他主要用来替换方法体。通过首先定义一个MethodReplacer接口实现,然后如下配置来实现:


java代码:
  1. <replaced-method name="方法名" replacer="MethodReplacer实现">  

  2. <arg-type>参数类型</arg-type>  

  3. </replaced-method>”  

      1)首先定义MethodReplacer实现,完全替换掉被替换方法的方法体及返回值,其中reimplement方法重定义方法 功能,参数obj为被替换方法的对象,method为被替换方法,args为方法参数;最需要注意的是不能再 通过“method.invoke(obj, new String[]{"hehe"});” 反射形式再去调用原来方法,这样会产生循环调用;如果返回值类型为Void,请在实现中返回null:


java代码:
  1. package cn.javass.spring.chapter3.bean;  

  2. import java.lang.reflect.Method;  

  3. import org.springframework.beans.factory.support.MethodReplacer;  

  4. publicclass PrinterReplacer implements MethodReplacer {  

  5. @Override

  6. public Object reimplement(Object obj, Method method, Object[] args)   throws Throwable {  

  7.        System.out.println("Print Replacer");  

  8. //注意此处不能再通过反射调用了,否则会产生循环调用,知道内存溢出

  9. //method.invoke(obj, new String[]{"hehe"});

  10. returnnull;  

  11.    }  

  12. }  


      2)配置如下,首先定义MethodReplacer实现,使用< replaced-method >标签来指定要进行替换方法,属性name指定替换的方法名字,replacer指定该方法的重新实现者,子标签< arg-type >用来指定原来方法参数的类型,必须指定否则找不到原方法:


java代码:
  1. <bean id="replacer"class="cn.javass.spring.chapter3.bean.PrinterReplacer"/>  

  2. <bean id="printer"class="cn.javass.spring.chapter3.bean.Printer">  

  3. <replaced-method name="print" replacer="replacer">  

  4.        <arg-type>java.lang.String</arg-type>  

  5.    </replaced-method>  

  6. </bean>  


      3)测试代码将输出“Print Replacer ”,说明方法体确实被替换了:


java代码:
  1. @Test

  2. publicvoid testMethodReplacer() {  

  3.    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("chapter3/methodReplacerInject.xml");  

  4.    Printer printer = context.getBean("printer", Printer.class);  

  5.    printer.print("我将被替换");  

  6. }