当前位置: 首页>编程语言>正文

Spring new spring new products

调用构造程序创建 Bean

首先,假定你打算开发一个在线销售产品的购物应用程序。先创建一个 Product类,这个类有多个属性,例如产品名称和价格。因为商店中有许多类型的产品,所以你定义 Product 类为抽象类,用于不同产品子类的扩展。

这次我们新建一个名为“sesametech.springrecipes.s006”包,在该包下创建“Product”类,主要代码如下:

public  abstract class Product {
 
       private String  name;
       private  double price;
 
       public Product() {
       }
 
       public Product(String name, double price) {
              this.name = name;
              this.price = price;
       }
 
 
@Override
       public String toString() {
              return  name + " " +  price;
       }
 
       getter & setter …
 
}

然后创建两个产品子类 Battery(电池)和 Disk(光盘),每个类都有自己的属性,主要代码分别如下:

public  class Battery extends Product {
       
       private  boolean rechargeable;  // 是否为可充电电池
       
       public Battery() {
              super();
       }
       
       public Battery(String name, double price) {
              super(name, price);
       }
 
       getter & setter …
 
}
 
public  class Disk extends Product {
       
       private  int capacity; // 容量
       
       public Disk() {
              super();
       }
       
       public Disk(String  name, double  price) {
              super(name, price);
       }
 
       getter & setter …
 
}

 

为了在 Spring IOC 容器中定义这些产品,我们在“sesametech.springrecipes.s006”包下创建“beans.xml”配置文件,内容如下:

<bean
id="aaa"
class="sesametech.springrecipes.s006.Battery">
<!-- 通过设值方法配置 Bean 属性 -->
<property name="name"value="AAA" />
<property name="price"value="2.5" />
<property name="rechargeable"value="true" />
</bean>
       
<bean
id="cdrw"
class="sesametech.springrecipes.s006.Disk">
<!-- 通过设值方法配置 Bean 属性 -->
<property name="name"value="CD-RW" />
<property name="price"value="1.5" />
<property name="capacity"value="1024" />
</bean>

最后,我们在“sesametech.springrecipes.s006”包下,创建“ProductTest”测试类并测试它,主要代码如下:

@SuppressWarnings("resource")
       public  static void main(String[]  args) {
context = newClassPathXmlApplicationContext("sesametech/springrecipes/s006/beans.xml");
product = (Product)  context.getBean("aaa");
              System.out.println(product.toString());
              
product = (Product)  context.getBean("cdrw");
              System.out.println(product.toString());
   }

 

现在,我们来大概分析一下这个应用程序。首先,它没有指定任何<constructor-arg> 标签来配置 Bean 属性,这样Spring 将会默认调用不带参数的构造方法来实例化相关的类,并通过设值方法注入属性值。前面的 Bean 配置等价于如下代码片段:

aaa = new Battery();
aaa.setName("AAA");
aaa.setPrice(2.5);
aaa.setRechargeable(true);
              
cdrw = new Disk();
cdrw.setName("CD-RW");
cdrw.setPrice(1.5);
cdrw.setCapacity(1024);

   但是,如果有一个或者多个 <constructor-arg> 标签,Spring  将匹配参数最合适的构造方法。作为例子,我们将前面的“beans.xml”配置文件拷贝一份,并更名为“beans-constructor.xml”,并按如下所示修改配置文件:

<bean
id="aaa"
class="sesametech.springrecipes.s006.Battery">
<constructor-arg value="AAA"/>
<constructor-arg value="2.5"/>
<property name="rechargeable"value="true" />
</bean>
       
<bean
id="cdrw"
class="sesametech.springrecipes.s006.Disk">
<constructor-arg value="CD-RW"/>
<constructor-arg value="1.5"/>
<property name="capacity"value="1024" />
</bean>

同时,我们修改一下“ProductTest”测试类中 main() 方法里的应用程序上下文加载配置文件的路径,如下所示:

//ApplicationContext context = new ClassPathXmlApplicationContext("sesametech/springrecipes/s006/beans.xml");
context = newClassPathXmlApplicationContext("sesametech/springrecipes/s006/beans-constructor.xml");

 

此时,看起来运行一切正常。因为 Product 和子类在构造方法上没有任何歧义,前述的 Bean 配置等价于下面的代码片段:

aaa = new Battery("AAA", 2.5);
aaa.setRechargeable(true);
              
cdrw = new Disk("CD-RW", 1.5);
cdrw.setCapacity(1024);

 

解决构造方法歧义

当你为 Bean 指定一个或者多个构造方法时参数时,Spring将试图在 Bean 类中寻找对应的构造方法并传递用于实例化Bean 的参数。但是,如果你的参数可以同时适用多个构造方法,就可能会造成 Spring 在匹配构造方法时出现歧义。这样的话,Spring 可能无法调用你所预期的构造方法。为了解决这个问题,Spring给出一种解决方案,允许你为 <constructor-arg> 标签指定type 和 index 属性,以帮助 Spring 查找预期的构造方法。

作为示例,我们在上面的“Disk”子类中增加一个“type”属性,同时增加一个构造方法,并覆写“Product”父类的toString() 方法,代码片段如下:

     

private  int capacity;  // 容量
       private String  type; // 类型
       
       public Disk() {
              super();
       }
       
       public Disk(String  name, String type) {
              this.setName(name);
              this.setType(type);
       }
       
       public Disk(String  name, double  price) {
              super(name, price);
       }
       
@Override
       public String toString() {
              return  super.toString() + " " +  type + " " +  capacity;
       }
       ……

然后,在“beans-constructor.xml”配置文件中增加一个 ID 为“dvdrw”的 bean 定义,如下所示:

<bean
id="dvdrw"
class="sesametech.springrecipes.s006.Disk">
<constructor-arg value="DVD-RW"/>
<constructor-arg value="3.5"/>
<property name="type" value="DVD"/>
<property name="capacity"value="4096" />
</bean>

最后,我们在“ProductTest”测试类中,增加以下代码:          

           

Diskdvdrw = (Disk)  context.getBean("dvdrw");
              System.out.println(dvdrw.toString());

我们的意图是,配置一个 name 为“DVD-RW”、price为“3.5”、type 为“DVD”、capacity 是“4096”的产品,但此时你若运行此程序将会得到如下结果:

AAA 2.5

CD-RW 0.0 1.5 1024

DVD-RW 0.0 DVD 4096

price 变成了“0.0”并且 CD-RW 的 type 变成了“1.5”。这个意外结果的起因是调用了第一个带有 name 和 type 参数的构造方法,而不是第二个带有 name 和 price 参数的构造方法。这是因为 Spring 默认将两个参数都解析为String 类型,而第一个构造方法不需要类型转换,因此被认定为最合适的。为了达到我们的预期,你必须设置<constructor-arg> 中的 type 属性。因此,我们把配置文件修改成如下所示:

<bean
id="cdrw"
class="sesametech.springrecipes.s006.Disk">
<constructor-arg type="java.lang.String"value="CD-RW" />
<constructor-arg type="double" value="1.5" />
<property name="type"value="CD" />
<property name="capacity"value="1024" />
</bean>
       
<bean
id="dvdrw"
class="sesametech.springrecipes.s006.Disk">
<constructor-arg type="java.lang.String"value="DVD-RW" />
<constructor-arg type="double"value="3.5" />
<property name="type"value="DVD" />
<property name="capacity"value="4096" />
</bean>

再次运行程序,如果不出意外,应该得到正常的结果,如下所示:

AAA 2.5

CD-RW 1.5 CD 1024

DVD-RW 3.5 DVD 4096

现在,我们再为“Disk”子类增加一个“discount”折扣属性,然后再增加一个新的构造方法,如下所示:

public  class Disk extends Product {
       
       private  int capacity;  // 容量
       private String  type; // 类型
       private  double discount;  // 折扣
       
       public Disk() {
              super();
       }
       
       public Disk(String  name, String type) {
              this.setName(name);
              this.setType(type);
       }
       
       public Disk(String  name, double  price) {
              super(name, price);
       }
       
       public Disk(double discount, String name) {
              this.setDiscount(discount);
              this.setName(name);
       }
       
@Override
       public String toString() {
              return  super.toString() + " " +  discount + " " +  type + " " +  capacity;
       }

getter & setter …
}

此时,再次运行程序,可能得到正确的这结果,或者得到如下意外的结果:

AAA 2.5

CD-RW 0.0 1.5 CD 1024

DVD-RW 0.0 3.5 DVD 4096

price 为“0.0”,而本应该是 price 的值却变成了 discount 了。这种不确定性的起因是 Spring 在内部对每个构造方法与参数的兼容性评分造成的。但Spring 在评分过程中并没有考虑参数出现在 XML 配置文件中的顺序。这意味着,Spring对第一个构造方法和第三个构造方法的评分是相同的,而具体选择哪一个构造方法又取决于匹配的顺序。根据 JavaReflection API 中的 Class.getDeclaredConstructors() 方法,返回的构造方法顺序将是任意的、无序的,可能与声明的顺序不同或者恰好相同。所有这些因素导致了构造方法匹配中的歧义。

为了避免这个问题,你必须通过<constructor-arg> 中的 index 属性,明确指出参数的索引位置。设置了type 和 index 属性后,Spring 就可以精确地为 Bean 找到预期的构造方法。但是,如果相当确定构造方法不会导致歧义,就可以忽略type 和 index 属性设置。

我们再次将上面示例中的配置文件改成如下所示:

<bean
id="cdrw"
class="sesametech.springrecipes.s006.Disk">
<constructor-arg type="java.lang.String"index="0" value="CD-RW"/>
<constructor-arg type="double"index="1" value="1.5"/>
<property name="discount"value="0.7" />
<property name="type"value="CD" />
<property name="capacity"value="1024" />
</bean>
       
<bean
id="dvdrw"
class="sesametech.springrecipes.s006.Disk">
<constructor-arg type="java.lang.String"index="0" value="DVD-RW"/>
<constructor-arg type="double"index="1" value="3.5"/>
<property name="discount"value="0.9" />
<property name="type"value="DVD" />
<property name="capacity"value="4096" />
</bean>

好了,今天就写到这里了,继续加油~


https://www.xamrdz.com/lan/58s1921822.html

相关文章: