spring3学习笔记之(spring core 之DI 更多知识点(依赖检查和方法注入))

小虾米 阅读:632 2021-03-31 22:31:25 评论:0

 

依赖检查

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

 

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

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

<?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-2.5.xsd 
</beans>

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

 

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

 

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

<bean id="helloApi" class="cn.javass.spring.chapter2.helloworld.HelloImpl"/> 
<!-- 注意我们没有注入helloApi,所以测试时会报错 --> 
<bean id="bean" 
     class="cn.javass.spring.chapter3.bean.HelloApiDecorator" 
     dependency-check="objects"> 
<property name="message" value="Haha"/> 
</bean>
  注意由于我们没有注入bean需要的依赖“helloApi”,所以应该抛出异常UnsatisfiedDependencyException,表示没有发现满足的依赖:

package cn.javass.spring.chapter3; 
import java.io.IOException; 
import org.junit.Test; 
import org.springframework.beans.factory.UnsatisfiedDependencyException; 
import org.springframework.context.support.ClassPathXmlApplicationContext; 
public class DependencyCheckTest { 
@Test(expected = UnsatisfiedDependencyException.class) 
public void testDependencyCheckByObject() throws IOException { 
//将抛出异常 
    new ClassPathXmlApplicationContext("chapter3/dependency-check-object.xml"); 
    } 
}
三、simple: 对基本类型进行依赖检查,包括数组类型,其他依赖不报错;配置方式为:dependency-check="simple",以下配置中没有注入message属性,所以会抛出异常:
<bean id="helloApi" class="cn.javass.spring.chapter2.helloworld.HelloImpl"/> 
<!-- 注意我们没有注入message属性,所以测试时会报错 --> 
<bean id="bean" 
class="cn.javass.spring.chapter3.bean.HelloApiDecorator" 
     dependency-check="simple"> 
   <property name="helloApi" ref="helloApi"/> 
</bean>

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

<bean id="helloApi" class="cn.javass.spring.chapter2.helloworld.HelloImpl"/> 
<bean id="bean" 
class="cn.javass.spring.chapter3.bean.HelloApiDecorator" 
     dependency-check="all"> 
  <property name="helloApi" ref="helloApi"/> 
<property name="message" value="Haha"/> 
</bean>
依赖检查也可以通过“<beans>”标签中default-dependency-check属性来指定全局依赖检查配置。


方法注入

所谓方法注入其实就是通过配置方式覆盖或拦截指定的方法,通常通过代理模式实现。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”



传统方式和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字段记录访问次数:

package cn.javass.spring.chapter3.bean; 
public class Printer { 
    private int counter = 0; 
    public void print(String type) { 
        System.out.println(type + " printer: " + counter++); 
    } 
}
 HelloImpl5类用于打印欢迎信息,其中包括setter注入和方法注入,此处特别需要注意的是该类是抽象的,充分说明了需要容器对其进行子类化处理,还定义了一个抽象方法createPrototypePrinter用于创建“prototype”Bean,createSingletonPrinter方法用于创建“singleton”Bean,此处注意方法会被Spring拦截,不会执行方法体代码:

package cn.javass.spring.chapter3; 
import cn.javass.spring.chapter2.helloworld.HelloApi; 
import cn.javass.spring.chapter3.bean.Printer; 
public abstract class HelloImpl5 implements HelloApi { 
    private Printer printer; 
    public void sayHello() { 
        printer.print("setter"); 
        createPrototypePrinter().print("prototype"); 
    } 
    public abstract Printer createPrototypePrinter(); 
    public Printer createSingletonPrinter() { 
        System.out.println("该方法不会被执行,如果输出就错了"); 
        return new Printer(); 
    } 
    public void setPrinter(Printer printer) { 
        this.printer = printer; 
    } 
}
2)  开始配置了,配置文件在(resources/chapter3/lookupMethodInject.xml),其中“prototypePrinter”是“prototype”Printer,“singletonPrinter”是“singleton”Printer,“helloApi1”是“singleton”Bean,而“helloApi2”注入了“prototype”Bean:

<bean id="prototypePrinter" 
class="cn.javass.spring.chapter3.bean.Printer" scope="prototype"/> 
<bean id="singletonPrinter" 
class="cn.javass.spring.chapter3.bean.Printer" scope="singleton"/> 
<bean id="helloApi1" class="cn.javass.spring.chapter3.HelloImpl5" scope="singleton"> 
<property name="printer" ref="prototypePrinter"/> 
<lookup-method name="createPrototypePrinter" bean="prototypePrinter"/> 
<lookup-method name="createSingletonPrinter" bean="singletonPrinter"/> 
</bean>           
<bean id="helloApi2" class="cn.javass.spring.chapter3.HelloImpl5" scope="prototype"> 
<property name="printer" ref="prototypePrinter"/> 
<lookup-method name="createPrototypePrinter" bean="prototypePrinter"/> 
<lookup-method name="createSingletonPrinter" bean="singletonPrinter"/> 
</bean>       

 3)测试代码如下:

package cn.javass.spring.chapter3; 
import org.junit.Test; 
import org.springframework.context.support.ClassPathXmlApplicationContext; 
import cn.javass.spring.chapter2.helloworld.HelloApi; 
public class MethodInjectTest { 
@Test 
public void testLookup() { 
ClassPathXmlApplicationContext context = 
new ClassPathXmlApplicationContext("chapter3/lookupMethodInject.xml"); 
        System.out.println("=======singleton sayHello======"); 
        HelloApi helloApi1 = context.getBean("helloApi1", HelloApi.class); 
        helloApi1.sayHello(); 
        helloApi1 = context.getBean("helloApi1", HelloApi.class); 
        helloApi1.sayHello(); 
        System.out.println("=======prototype sayHello======"); 
        HelloApi helloApi2 = context.getBean("helloApi2", HelloApi.class); 
        helloApi2.sayHello(); 
        helloApi2 = context.getBean("helloApi2", HelloApi.class); 
        helloApi2.sayHello(); 
}}
其中“helloApi1”测试中,其输出结果如下:

=======singleton sayHello====== 
setter printer: 0 
prototype printer: 0 
singleton printer: 0 
setter printer: 1 
prototype printer: 0 
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”测试中,其输出结果如下:

	 
=======prototype sayHello====== 
setter printer: 0 
prototype printer: 0 
singleton printer: 2 
setter printer: 0 
prototype printer: 0 
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接口实现,然后如下配置来实现:

<replaced-method name="方法名" replacer="MethodReplacer实现"> 
<arg-type>参数类型</arg-type> 
</replaced-method>” 
 
  1)首先定义MethodReplacer实现,完全替换掉被替换方法的方法体及返回值,其中reimplement方法重定义方法 功能,参数obj为被替换方法的对象,method为被替换方法,args为方法参数;最需要注意的是不能再 通过“method.invoke(obj, new String[]{"hehe"});” 反射形式再去调用原来方法,这样会产生循环调用;如果返回值类型为Void,请在实现中返回null:
package cn.javass.spring.chapter3.bean; 
import java.lang.reflect.Method; 
import org.springframework.beans.factory.support.MethodReplacer; 
public class PrinterReplacer implements MethodReplacer { 
    @Override 
    public Object reimplement(Object obj, Method method, Object[] args)   throws Throwable { 
        System.out.println("Print Replacer"); 
        //注意此处不能再通过反射调用了,否则会产生循环调用,知道内存溢出 
        //method.invoke(obj, new String[]{"hehe"}); 
        return null; 
    } 
}
  2)配置如下,首先定义MethodReplacer实现,使用< replaced-method >标签来指定要进行替换方法,属性name指定替换的方法名字,replacer指定该方法的重新实现者,子标签< arg-type >用来指定原来方法参数的类型,必须指定否则找不到原方法:
<bean id="replacer" class="cn.javass.spring.chapter3.bean.PrinterReplacer"/> 
<bean id="printer" class="cn.javass.spring.chapter3.bean.Printer"> 
<replaced-method name="print" replacer="replacer"> 
        <arg-type>java.lang.String</arg-type> 
    </replaced-method> 
</bean>
 3)测试代码将输出“Print Replacer ”,说明方法体确实被替换了:
@Test 
public void testMethodReplacer() { 
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("chapter3/methodReplacerInject.xml"); 
    Printer printer = context.getBean("printer", Printer.class); 
    printer.print("我将被替换"); 
}







标签:Spring
声明

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

关注我们

一个IT知识分享的公众号