《Spring in action 4》(二)装配Bean

装配Bean

莫道君行早 更有早行人

Spring装配Bean的三种方式

  1. 在XML中进行显示配置
  2. 在Java中进行显示配置
  3. 隐式的Bean发现和自动装配
  • 组件扫描:Spring会自动发现应用上下文所创建的Bean
  • 自动装配:Spring自动满足Bean之间的依赖

​ 尽可能地的使用自动配置的机制。显示的配置越少越好,当你必须要显示配置Bean的时候(比如:有些源码不是由你来维护的,而当你需要为这些代码配置的时候),推荐使用类型安全(因为在Java配置中可以利用编译器检查)并且比XML更加强大的JavaConfig。最后,只有当你想要使用便利的XML命名空间,并且在JavaConfig中没有同样的实现时,才应该使用XML。

下面利用一个案例

​ 手机和手机电池是一个依赖关系,在正常情况下,如果手机离开了手机电池,那么其实也不会起到什么作用,故以此为一个案例展开Spring Bean的装配。

手机接口:

/**
 * 案例分析:
 * 手机是依赖于手机电池存在的。
 */
public interface MobilePhone {}

电池接口:

/*电池接口*/
public interface Battery {}

手机实现类:

public class HuaweiMobilePhone implements MobilePhone {
    private Battery battery;
    public HuaweiMobilePhone(){}

    public HuaweiMobilePhone(Battery battery){
        this.battery = battery;
    }
    @Override
    public String toString() {
        return "HuaweiMobilePhone{" +
                "battery=" + battery +
                '}';
    }
}

电池实现类:

public class HuaweiBattery implements Battery{
    private String brand;
    private String name;
    public HuaweiBattery(){}

    public HuaweiBattery(String brand, String name){
        this.brand = brand;
        this.name = name;
    }

    @Override
    public String toString() {
        return "HuaweiBattery{" +
                "brand='" + brand + '\'' +
                ", name='" + name + '\'' +
                '}';
    }
}

XML显示配置

下面展示了基本的Bean注入方式,我们在上一篇其实已经接触到了。

<?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="battery" class="com.ooyhao.spring.bean.HuaweiBattery"/>
    <bean id="huaweiMobilePhone" class="com.ooyhao.spring.bean.HuaweiMobilePhone">
        <constructor-arg name="battery" ref="battery"/>
    </bean>
</beans>

使用Xml方式,测试类:

@Test
public void testDIXml(){
  ClassPathXmlApplicationContext context
            = new ClassPathXmlApplicationContext("phone.xml");
  HuaweiMobilePhone bean = context.getBean(HuaweiMobilePhone.class);
  System.out.println(bean);
}

Java显示配置

PhoneConfig 配置类:

public class PhoneConfig {
    @Bean
    public HuaweiBattery huaweiBattery(){
        return new HuaweiBattery();
    }
    @Bean
    public HuaweiMobilePhone huaweiMobilePhone(){
        return new HuaweiMobilePhone(huaweiBattery());
    }
}

使用注解,测试,如下:

@Test
public void testAutoWire(){
    AnnotationConfigApplicationContext context
            = new AnnotationConfigApplicationContext(PhoneConfig.class);
    HuaweiMobilePhone phone = context.getBean(HuaweiMobilePhone.class);
    System.out.println(phone);
}

自动发现和装配

​ 使用Bean自动发现和装配的方式,则需要在Bean类上用@Component注解进行标识。通过@CompentScan(basePackages="")进行组件扫描。使用@AutoWired注解进行自动装配。

手机实现类:

@Component
public class HuaweiBattery implements Battery{
    private String brand;
    private String name;
    public HuaweiBattery(){}

    public HuaweiBattery(String brand, String name){
        this.brand = brand;
        this.name = name;
    }
    @Override
    public String toString() {
        return "HuaweiBattery{" +
                "brand='" + brand + '\'' +
                ", name='" + name + '\'' +
                '}';
    }
}

手机电池实现类:

@Component
public class HuaweiMobilePhone implements MobilePhone {
    @Autowired
    private Battery battery;
    public HuaweiMobilePhone(){}
    public HuaweiMobilePhone(Battery battery){
        this.battery = battery;
    }

    @Override
    public String toString() {
        return "HuaweiMobilePhone{" +
                "battery=" + battery +
                '}';
    }
}

Java配置类:

@ComponentScan(basePackages = "com.ooyhao.spring")
public class AutoWirePhoneConfig {}

测试类:

@Test
public void testAutoWire(){
  AnnotationConfigApplicationContext context
    = new AnnotationConfigApplicationContext(AutoWirePhoneConfig.class);
  HuaweiMobilePhone phone = context.getBean(HuaweiMobilePhone.class);
  System.out.println(phone);
}

总结

  • 创建应用对象之间的协作关系行为通常称为装配,这也是依赖注入(DI)的本质。
  • @ComponentScan默认会扫描与配置类相同的包。组件扫描默认是不启动的。@ComponentScan除了basePackages属性,还有basePackageClasses属性。
  • @Bean 注解会告诉Spring这个方法将会返回一个对象。默认情况下,bean的ID与带有@Bean注解的方法名一样。
  • @component 默认是使用类名首字母小写作为Bean的id。当然也可以用其属性自定义指定。

属性注入

构造注入

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="battery" class="com.ooyhao.spring.bean.HuaweiBattery">
        <constructor-arg name="brand" value="Huawei"/>
        <constructor-arg name="name" value="华为手机"/>
    </bean>

    <bean id="huaweiMobilePhone" class="com.ooyhao.spring.bean.HuaweiMobilePhone">
        <constructor-arg name="battery" ref="battery"/>
    </bean>
</beans>

测试实例不变,测试结果:

HuaweiMobilePhone{battery=HuaweiBattery{brand='Huawei', name='华为手机'}}

为了了解更多的类型注入,下面新增耳机接口和实现类,并将手机的实现类进行修改,如下:

耳机接口类:

/*耳机接口类*/
public interface EarPhone {}

耳机实现类:

public class HuaweiEarPhone implements EarPhone {

    private String color;
    private Double price;

    public HuaweiEarPhone() {}

    public HuaweiEarPhone(String color, Double price) {
        this.color = color;
        this.price = price;
    }

    @Override
    public String toString() {
        return "HuaweiEarPhone{" +
                "color='" + color + '\'' +
                ", price=" + price +
                '}';
    }
}

手机类:增加手机颜色和耳机

public class HuaweiMobilePhone implements MobilePhone {

    private Battery battery;
    //手机颜色
    private List<String> colors;
    private Set<EarPhone> earPhones;

    public HuaweiMobilePhone(){}

    public HuaweiMobilePhone(Battery battery){
        this.battery = battery;
    }

    public HuaweiMobilePhone(List<String> colors) {
        this.colors = colors;
    }

    public HuaweiMobilePhone(Battery battery, List<String> colors){
        this.battery = battery;
        this.colors = colors;
    }

    public HuaweiMobilePhone(Battery battery,List<String> colors, 
                             Set<EarPhone> earPhones) {
        this.battery = battery;
        this.colors = colors;
        this.earPhones = earPhones;
    }

    @Override
    public String toString() {
        return "HuaweiMobilePhone{" +
                "battery=" + battery +
                ", colors=" + colors +
                ", earPhones=" + earPhones +
                '}';
    }
}

修改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"
       xmlns:c="http://www.springframework.org/schema/c"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="battery" class="com.ooyhao.spring.bean.HuaweiBattery">
        <constructor-arg name="brand" value="Huawei"/>
        <constructor-arg name="name" value="华为手机"/>
    </bean>

    <!--显示用name和value进行指定参数-->
    <bean id="earPhone1" class="com.ooyhao.spring.bean.HuaweiEarPhone">
        <constructor-arg name="color" value="白色"/>
        <constructor-arg name="price" value="59.9"/>
    </bean>

    <!--通过默认的Index顺序,顺序不能错-->
    <bean id="earPhone2" class="com.ooyhao.spring.bean.HuaweiEarPhone">
        <constructor-arg value="黑色"/>
        <constructor-arg value="60.5"/>
    </bean>

    <!--通过显示指定Index,顺序可以随意调换-->
    <bean id="earPhone3" class="com.ooyhao.spring.bean.HuaweiEarPhone">
        <constructor-arg index="1" value="66.6"/>
        <constructor-arg index="0" value="粉色"/>
    </bean>

    <!--通过c-index命名空间-->
    <bean id="earPhone4" class="com.ooyhao.spring.bean.HuaweiEarPhone"
        c:_0="蓝色" c:_1="88.8"/>

    <!--通过c-name命名空间-->
    <bean id="earPhone5" class="com.ooyhao.spring.bean.HuaweiEarPhone"
        c:color="绿色" c:price="90.8"/>

    <bean id="huaweiMobilePhone" class="com.ooyhao.spring.bean.HuaweiMobilePhone">
        <!--手机电池 引用构造注入-->
        <constructor-arg name="battery" ref="battery"/>
        <!--手机颜色 集合字面量注入-->
        <constructor-arg>
            <list>
                <value>蓝色</value>
                <value>红色</value>
                <value>黑色</value>
            </list>
        </constructor-arg>
        <!--手机耳机 集合Bean注入-->
        <constructor-arg name="earPhones">
            <set>
                <ref bean="earPhone1"/>
                <ref bean="earPhone2"/>
                <ref bean="earPhone3"/>
                <ref bean="earPhone4"/>
                <ref bean="earPhone5"/>
            </set>
        </constructor-arg>
    </bean>
</beans>

测试结果如下:

HuaweiMobilePhone{battery=HuaweiBattery{brand='Huawei', name='华为手机'}, colors=[蓝色, 红色, 黑色], earPhones=[HuaweiEarPhone{color='白色', price=59.9}, HuaweiEarPhone{color='黑色', price=60.5}, HuaweiEarPhone{color='粉色', price=66.6}, HuaweiEarPhone{color='蓝色', price=88.8}, HuaweiEarPhone{color='绿色', price=90.8}]}

Set注入

​ 下面案例展示如何使用Set方法进行属性注入,所以需要将 手机实现类、电池实现类和手机耳机实现类均添加上set方法。(此处不展示了)

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"
       xmlns:c="http://www.springframework.org/schema/c"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="battery" class="com.ooyhao.spring.bean.HuaweiBattery">
        <property name="brand" value="Huawei"/>
        <property name="name" value="华为手机"/>
    </bean>

    <!--显示用name和value进行指定参数-->
    <bean id="earPhone1" class="com.ooyhao.spring.bean.HuaweiEarPhone">
        <property name="color" value="白色"/>
        <property name="price" value="59.9"/>
    </bean>

    <!--通过p-name命名空间-->
    <bean id="earPhone2" class="com.ooyhao.spring.bean.HuaweiEarPhone"
       p:color="蓝色" p:price="88.88"/>

    <bean id="huaweiMobilePhone" class="com.ooyhao.spring.bean.HuaweiMobilePhone">
        <!--手机电池 引用set注入-->
        <property name="battery" ref="battery"/>
        <!--手机颜色 集合字面量注入-->
        <property name="colors">
            <set>
                <value>蓝色</value>
                <value>红色</value>
                <value>黑色</value>
            </set>
        </property>
        <!--手机耳机 集合引用类型注入-->
        <property name="earPhones">
            <list>
                <ref bean="earPhone1"/>
                <ref bean="earPhone2"/>
            </list>
        </property>
    </bean>
</beans>

测试结果:

HuaweiMobilePhone{battery=HuaweiBattery{brand='Huawei', name='华为手机'}, colors=[蓝色, 红色, 黑色], earPhones=[HuaweiEarPhone{color='白色', price=59.9}, HuaweiEarPhone{color='蓝色', price=88.88}]}

注意:

​ 可以使用<null/> 或者 <null></null> 标签可以将null注入。

set 和 list 的区别

<set><list> 元素的区别不大,其中最重要的不同在于当Spring创建要装配的集合时,所创建的时java.util.Set 还是 java.util.List。 如果时Set的话,所有重复值都会被忽略掉,存放顺序也不会得以保证。不过无论在哪种情况下,<set><list 都可以用来装配List和Set甚至时数组。

p 和 c-arg 区别

<property> 元素为属性的setter方法所提供的功能与 <constructor-arg> 元素为构造器所提供的功能时一样的。Spring为<Constructor-arg> 元素提供了c-命名空间作为替代方案,与之类似,Spring提供了更加简洁的p-命名空间,作为 <property> 元素的替代方案。

导入和混合配置

​ 在实际开发中,也许时Xml显示配置和JavaConfig显示配置都存在,并且需要XML与XML相互引用,甚至的XML和JavaConfig交叉引用。所以,下面我们来看一下如何进行相互使用:

XML配置文件相互引用

手机实现类:


public class HuaweiMobilePhone implements MobilePhone {

    private Battery battery;

    //手机颜色
    private List<String> colors;

    public HuaweiMobilePhone(){}
  
    public HuaweiMobilePhone(Battery battery){
        this.battery = battery;
    }

    public HuaweiMobilePhone(List<String> colors) {
        this.colors = colors;
    }

    public HuaweiMobilePhone(Battery battery, List<String> colors){
        this.battery = battery;
        this.colors = colors;
    }
    public Battery getBattery() {
        return battery;
    }

    public void setBattery(Battery battery) {
        this.battery = battery;
    }

    public List<String> getColors() {
        return colors;
    }

    public void setColors(List<String> colors) {
        this.colors = colors;
    }

    @Override
    public String toString() {
        return "HuaweiMobilePhone{" +
                "battery=" + battery +
                ", colors=" + colors +
                '}';
    }
}

电池实现类:

public class HuaweiBattery implements Battery{

    private String brand;

    private String name;

    public HuaweiBattery(){}

    public HuaweiBattery(String brand, String name){
        this.brand = brand;
        this.name = name;
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "HuaweiBattery{" +
                "brand='" + brand + '\'' +
                ", name='" + name + '\'' +
                '}';
    }
}

电池XMl:

<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="huaweiBattery" class="com.ooyhao.spring.bean.HuaweiBattery">
        <property name="brand" value="Huawei"/>
        <property name="name" value="华为荣耀手机"/>
    </bean>

</beans>

手机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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <import resource="Battery.xml"/>

    <bean id="huaweiMobilePhone" class="com.ooyhao.spring.bean.HuaweiMobilePhone">
        <!--手机电池 引用构造注入-->
        <constructor-arg name="battery" ref="huaweiBattery"/>
        <!--手机颜色 集合字面量注入-->
        <constructor-arg>
            <list>
                <value>蓝色</value>
                <value>红色</value>
                <value>黑色</value>
            </list>
        </constructor-arg>
    </bean>

</beans>

测试:

@Test
public void testRef(){
    ClassPathXmlApplicationContext context
             = new ClassPathXmlApplicationContext("phone.xml");
    HuaweiMobilePhone bean = context.getBean(HuaweiMobilePhone.class);
    System.out.println(bean);
}
//HuaweiMobilePhone{battery=HuaweiBattery{brand='Huawei', name='华为荣耀手机'}, colors=[蓝色, 红色, 黑色]}

​ 在实际开发中,不可能将所有的配置信息写到一个XML文件中,所以存在相互引用,上述代码可以测试出,在xml文件
如何引用另外一个xml文件。一般,可以单独创建一个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">

    <import resource="Battery.xml"/>
    <import resource="phone.xml"/>

</beans>

此时,将手机xml文件中的<import resource="Battery.xml"/> 删除,而测试引用的使用引用合并的xml文件。如下:

@Test
public void testRef(){
    ClassPathXmlApplicationContext context
             = new ClassPathXmlApplicationContext("combineXml.xml");
    HuaweiMobilePhone bean = context.getBean(HuaweiMobilePhone.class);
    System.out.println(bean);
}

Java配置类相互引用

BatteryConfig配置类:

public class BatteryConfig {
    @Bean
    public HuaweiBattery huaweiBattery(){
        return new HuaweiBattery("Huawei","华为手机电池");
    }
}

PhoneConfig配置类:

@Import({BatteryConfig.class})
public class PhoneConfig {

    @Bean
    public HuaweiMobilePhone huaweiMobilePhone(Battery huaweiBattery){
        List<String> colors = new ArrayList<String>();
        colors.add("白色");
        colors.add("黑色");
        colors.add("粉色");
        return new HuaweiMobilePhone(huaweiBattery,colors);
    }
}

测试类:

@Test
public void testJavaConfigRef(){
    AnnotationConfigApplicationContext context
            = new AnnotationConfigApplicationContext(PhoneConfig.class);
    HuaweiMobilePhone bean = context.getBean(HuaweiMobilePhone.class);
    System.out.println(bean);
    //HuaweiMobilePhone{battery=HuaweiBattery{brand='Huawei', name='华为手机电池'}, colors=[白色, 黑色, 粉色]}
}

​ 上述代码可以看出,在JavaConfig配置类之间互相引用其实和Xml文件非常相似,在XML文件中是使用<import resource = 'aa.xml' 而在JavaConfig配置类中导入是使用@import({Config.class})注解。通过导入的配置类的class类即可。当然,随着项目的不断扩大,JavaConfig配置类也会越来越多,这种方式来维护就会变得比较麻烦,我们还是可以通过使用统一的JavaConfig配置类来管理JavaConfig。如下:

@Import({
        BatteryConfig.class,
        PhoneConfig.class
})
public class CombineConfig {}

Java配置类中引用Xml配置文件

上面已经介绍完了Java配置如何引用Java配置,Xml配置如何引用Xml配置,并且如何将其分散的配置文件或配置类
进行统一的处理。下面我们看一下如何在Java配置类中引用Xml配置文件:

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="huaweiBattery" class="com.ooyhao.spring.bean.HuaweiBattery">
        <property name="brand" value="Huawei"/>
        <property name="name" value="华为荣耀手机"/>
    </bean>

</beans>

在Java配置类中引入Xml配置文件信息:

@ImportResource({"Battery.xml"})
public class PhoneConfig {

    @Bean
    public HuaweiMobilePhone huaweiMobilePhone(Battery huaweiBattery){
        List<String> colors = new ArrayList<String>();
        colors.add("白色");
        colors.add("黑色");
        colors.add("粉色");
        return new HuaweiMobilePhone(huaweiBattery,colors);
    }
}

由上述代码可以看出,使用@ImportResource标签来导入一个或多个Xml配置文件,(所以代码中的配置文件
默认存放在ClassPath的根目录下,如果有异,需要自行修改)。

Xml配置文件中引用Java配置类

电池配置类:

public class BatteryConfig {
    @Bean
    public HuaweiBattery huaweiBattery(){
        return new HuaweiBattery("Huawei","华为手机电池");
    }
}

在Xml配置文件中引入JavaConfig配置类,如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <bean class="com.ooyhao.spring.config.BatteryConfig"/>

    <context:component-scan base-package="com.ooyhao.spring.config"/>

    <bean id="huaweiMobilePhone" class="com.ooyhao.spring.bean.HuaweiMobilePhone">
        <!--手机电池 引用构造注入-->
        <constructor-arg name="battery" ref="huaweiBattery"/>
        <!--手机颜色 集合字面量注入-->
        <constructor-arg>
            <list>
                <value>蓝色</value>
                <value>红色</value>
                <value>黑色</value>
            </list>
        </constructor-arg>
    </bean>
</beans>

由上述Xml配置文件中可以看出,使用<bean>标签将配置文件导入,则可以在ref中引用到,但是此时测试还是会报错误,需要使用<component-scan> 配合使用,测试代码就不展示了。

总结

​ 至此,本节内容就算结束了,其中包括使用 Xml配置文件形式装配Bean,使用 JavaConfig配置文件形式装配Bean, 以及 使用自动发现和装配的方式来装配Bean 三种方式。同时涉及了 XMl文件中属性注入的两种方式,构造注入和set注入,JavaConfig配置文件中注入并未做过多的显示,因为这种方式其实跟普通的代码类似,不做主要记录。当然如果需要单独的引用properties配置文件的内容来装配字面量,可以使用@Value(${})来操作。

​ 基于实际开发环境的复杂度,往往上述三种方式都会在一个项目中共存的,那么如何在Xml中引用Xml,如何在JavaConfig配置文件中引用JavaConfig配置类,如何在Xml配置文件中引用JavaConfig配置类,如何在JavaConfig配置类中引用Xml配置文件。

案例地址:https://gitee.com/ooyhao/JavaRepo_Public/tree/master/Spring-in-Action

最后

如果觉得不错的话,那就关注一下小编哦!一起交流,一起学习

posted @ 2019-09-17 09:43  ooyhao  阅读(233)  评论(0编辑  收藏  举报