Spring学习(四)---装配Bean

一、概述

Spring 中提供了 3 种方法进行配置:让我们将自己开发的 Bean 装配到 Spring IoC 容器中

  • 在 XML 文件中显式配置
  • 在 Java 的接口和类中实现配置
  • 隐式 Bean 的发现机制和自动装配原则

方式选择的原则

1、最优先:通过隐式 Bean 的发现机制和自动装配的原则。基于约定由于配置的原则,这种方式应该是最优先的

  • 好处:减少程序开发者的决定权,简单又不失灵活。

2、其次:Java 接口和类中配置实现配置。在没有办法使用自动装配原则的情况下应该优先考虑此类方法

  • 好处:避免 XML 配置的泛滥,也更为容易。
  • 典型场景:一个父类有多个子类,比如学生类有两个子类,一个男学生类和女学生类,通过 IoC 容器初始化一个学生类,容器将无法知道使用哪个子类去初始化,这个时候可以使用 Java 的注解配置去指定。

3、最后:XML 方式配置、在上述方法都无法使用的情况下,那么也只能选择 XML 配置的方式。

  • 好处:简单易懂(当然,特别是对于初学者)
  • 典型场景:当使用第三方类的时候,有些类并不是我们开发的,我们无法修改里面的代码,这个时候就通过 XML 的方式配置使用了。

二、通过 XML 配置装配 Bean

1、直接上例子:一个简单的例子

<bean id="car" class="com.dongtian.TestSpring.pojo.Car">
  	<property name="name" value="奥迪"></property>
  	<property name="color" value="blue"></property>
  	<property name="age" value="1"></property>
</bean>
  • id 属性是 Spring 能找到这个 Bean 的编号,遵守 XML 语法的 ID 唯一性约束不过 id 属性不是一个必需的属性,name 属性也可以定义 bean 元素的名称。
  • class 属性显然就是一个类的全限定名
  • property 元素是定义类的属性,其中的 name 属性定义的是属性的名称,而 value 是它的值。

但是有时候需要注入一些自定义的类,比如果汁制造机需要用户提供原料信息才能完成果汁的制作:

<!-- 配置 srouce 原料 -->
<bean name="source" class="pojo.Source">
    <property name="fruit" value="橙子"/>
    <property name="sugar" value="多糖"/>
    <property name="size" value="超大杯"/>
</bean>

<bean name="juickMaker" class="pojo.JuiceMaker">
    <!-- 注入上面配置的id为srouce的Srouce对象 -->
    <property name="source" ref="source"/>
</bean>

这里先定义了一个 name 为 source 的 Bean,然后再制造器中通过 ref 属性去引用对应的 Bean,而 source 正是之前定义的 Bean 的 name ,这样就可以相互引用了。注入对象:使用 ref 属性

2、有些时候我们需要装配一些复杂的东西,比如 Set、Map、List、Array 和 Properties 等,直接看例子:

<bean id="complexAssembly" class="pojo.ComplexAssembly">
    <!-- 装配Long类型的id -->
    <property name="id" value="1"/>
    
    <!-- 装配List类型的list List 属性为对应的 <list> 元素进行装配,然后通过多个 <value> 元素设值-->
    <property name="list">
        <list>
            <value>value-list-1</value>
            <value>value-list-2</value>
            <value>value-list-3</value>
        </list>
    </property>
    
    <!-- 装配Map类型的map Map 属性为对应的 <map> 元素进行装配,
            然后通过多个 <entry> 元素设值,只是 entry 包含一个键值对(key-value)的设置-->
    <property name="map">
        <map>
            <entry key="key1" value="value-key-1"/>
            <entry key="key2" value="value-key-2"/>
            <entry key="key3" value="value-key-2"/>
        </map>
    </property>
    
    <!-- 装配Properties类型的properties Properties 属性为对应的 <properties> 元素进行装配,
        通过多个 <property> 元素设值,只是 properties 元素有一个必填属性 key ,然后可以设置值-->
    <property name="properties">
        <props>
            <prop key="prop1">value-prop-1</prop>
            <prop key="prop2">value-prop-2</prop>
            <prop key="prop3">value-prop-3</prop>
        </props>
    </property>
    
    <!-- 装配Set类型的set -->
    <property name="set">
        <set>
            <value>value-set-1</value>
            <value>value-set-2</value>
            <value>value-set-3</value>
        </set>
    </property>
    
    <!-- 装配String[]类型的array -->
    <property name="array">
        <array>
            <value>value-array-1</value>
            <value>value-array-2</value>
            <value>value-array-3</value>
        </array>
    </property>
</bean>

如果集合中存在引用类型,加入了 ref 这一个属性而已:

<!--list属性使用 <list> 元素定义注入,使用多个 <ref> 元素的Bean属性去引用之前定义好的Bean-->
<property name="list">
    <list>
        <ref bean="bean1"/>
        <ref bean="bean2"/>
    </list>
</property>

<!--Map 属性使用 <map> 元素定义注入,使用多个 <entry> 元素的 key-ref 属性去引用之前定义好的 Bean 作为键,而用 value-ref 属性引用之前定义好的 Bean 作为值-->
<property name="map">
    <map>
        <entry key-ref="keyBean" value-ref="valueBean"/>
    </map>
</property>

<!--Set属性使用 <set> 元素定义注入,使用多个 <ref> 元素的bean去引用之前定义好的 Bean-->
<property name="set">
    <set>
        <ref bean="bean"/>
    </set>
</property>

3、命名空间装配

 Spring 还提供了对应的命名空间的定义,只是在使用命名空间的时候要先引入对应的命名空间和 XML 模式(XSD)文件。

① c-命名空间:c-命名空间通过构造器注入的方式来配置 bean。要使用它,首先需要引入

怎么用看例子就能懂的:

<!-- 引入 c-命名空间之前 -->
<bean name="student1" class="pojo.Student">
    <constructor-arg name="id" value="1" />
    <constructor-arg name="name" value="学生1"/>
</bean>

<!-- 引入 c-命名空间之后 -->
<bean name="student2" class="pojo.Student"
      c:id="2" c:name="学生2"/>

<!--将参数的名称替换成了 “0” 和 “1” ,也就是参数的索引,必须要添加一个下划线来作为前缀-->
<bean name="student2" class="pojo.Student"
      c:_0="3" c:_1="学生3"/>

如果是引用:在需要注入对象的话则要跟上 -ref,c:card-ref="idCard1"

② p-命名空间:p-命名空间则是用setter的注入方式来配置 bean 。

同样看例子就能懂:

<!-- 引入p-命名空间之前 -->
<bean name="student1" class="pojo.Student">
    <property name="id" value="1" />
    <property name="name" value="学生1"/>
</bean>

<!-- 引入p-命名空间之后 -->
<bean name="student2" class="pojo.Student" 
      p:id="2" p:name="学生2"/>

<bean name="student2" class="pojo.Student"
          p:id="2" p:name="学生2" p:cdCard-ref="cdCard1"/>

③ util-命名空间:工具类的命名空间,可以简化集合类元素的配置:

举例其中一个:<util:list>

<!-- 引入util-命名空间之前 -->
<property name="list">
    <list>
        <ref bean="bean1"/>
        <ref bean="bean2"/>
    </list>
</property>

<!-- 引入util-命名空间之后 -->
<util:list id="list">
    <ref bean="bean1"/>
    <ref bean="bean2"/>
</util:list>

三、通过注解装配 Bean

1、在 Spring 中,它提供了两种方式来让 Spring IoC 容器发现 bean:

  • 组件扫描:通过定义资源的方式,让 Spring IoC 容器扫描对应的包,从而把 bean 装配进来。
  • 自动装配:通过注解定义,使得一些依赖关系可以通过注解完成。
  • 优势:
    1、可以减少 XML 的配置,当配置项多的时候,臃肿难以维护
    2、功能更加强大,既能实现 XML 的功能,也提供了自动装配的功能,采用了自动装配后,程序猿所需要做的决断就少了,更加有利于对程序的开发,这就是“约定由于配置”的开发原则

1、使用@Component 装配 Bean:而其中的 value 属性代表这个类在 Spring 中的 id

2、使用@Value注解:表示值的注入

@Component(value="car")
public class Car {
	@Value("奔驰")
	private String name;
	@Value("红色")
	private String color;
    // setter and getter
    //....
}

这样我们就声明好了我们要创建的一个 Bean,就像在 XML 中写下了这样一句话:

<bean id="car" class="com.dongtian.TestSpring.pojo.Car">
     <property name="name" value="奔驰"></property>
     <property name="color" value="红色"></property>
</bean>

但是现在我们声明了这个类,并不能进行任何的测试,因为 Spring IoC 并不知道这个 Bean 的存在,这个时候我们可以使用一个 PojoConfig 类去告诉 Spring IoC :

import org.springframework.context.annotation.ComponentScan;

@ComponentScan
public class PojoConfig {}

这个类十分简单,没有任何逻辑,但是需要说明两点:

  • 该类和 Student 类位于同一包名下
  • @ComponentScan注解:代表进行扫描,默认是扫描当前包的路径,扫描所有带有 @Component 注解的 POJO。

这样一来,我们就可以通过 Spring 定义好的 Spring IoC 容器的实现类——AnnotationConfigApplicationContext 去生成 IoC 容器了:

这里可以看到使用了 AnnotationConfigApplicationContext 类去初始化 Spring IoC 容器,它的配置项是PojoConfig 类,这样 Spring IoC 就会根据注解的配置去解析对应的资源,来生成 IoC 容器了。

public class TestCar {
	public static void main(String[] args) {		
		ApplicationContext ctx = 
				new AnnotationConfigApplicationContext(PojoConfig.class); 
		Car car = ctx.getBean(Car.class);
		System.out.println(car);
	}
}

存在两个明显的弊端:

  •  @ComponentScan 注解,它只是扫描所在包的 Java 类,但是更多的时候我们希望的是可以扫描我们指定的类
  •  测试发现,通过 @Value 注解并不能注入对象

第一个问题好解决,因为@ComponentScan 注解存在着两个配置项:

  • basePackages:它是由 base 和 package 两个单词组成的,而 package 还是用了复数,意味着它可以配置一个 Java 包的数组,Spring 会根据它的配置扫描对应的包和子包,将配置好的 Bean 装配进来
  • basePackageClasses:它由 base、package 和 class 三个单词组成,采用复数,意味着它可以配置多个类, Spring 会根据配置的类所在的包,为包和子包进行扫描装配对应配置的 Bean

所有可以通过制定扫描多个包,多个类,两个选一个就能够解决问题

@ComponentScan(basePackages ={ "pojo","service"})

@ComponentScan(basePackageClasses = {Car.class, Person.class})

第二个问题可以通过自动装配将解决这个问题。

3、自动装配——@Autowired

     自动装配技术是一种由 Spring 自己发现对应的 Bean,自动完成装配工作的方式,它会应用到一个十分常用的注解 @Autowired 上,这个时候 Spring 会根据类型去寻找定义的 Bean 然后将其注入。

(1)我们的Pojo类是Student:

package com.dongtian.TestSpring.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component("student")
public class Student {
	@Value("4556")
	private String id;
	@Value("十三")
	private String name;
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}	
}

在service下创建一个 StudentService 接口:

package com.dongtian.TestSpring.service;

public interface StudentService {
	public void printStudentInfo();
}

接着创建一个 StudentServiceImp 实现类:

package com.dongtian.TestSpring.serviceImp;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.dongtian.TestSpring.pojo.Student;
import com.dongtian.TestSpring.service.StudentService;

@Component("studentService")
public class StudentServiceImp implements StudentService{
    @Autowired
    private Student student = null;
	public void printStudentInfo() {				
	     System.out.println("学生的 id 为:" + student.getId());
	     System.out.println("学生的 name 为:" + student.getName());
	}
}

这里的 @Autowired 注解,表示在 Spring IoC 定位所有的 Bean 后,这个字段需要按类型注入,这样 IoC 容器就会寻找资源,然后将其注入。

这里我不采用@ComponentScan,而是采用结合XML的方式扫描:在XML中添加

<context:component-scan base-package="com.dongtian.TestSpring.*"/>

测试方法和结果:

学生的 id 为:4556
学生的 name 为:十三

public class TestStudent {
	public static void main(String[] args) {
		ApplicationContext ctx = new ClassPathXmlApplicationContext("student-aContext.xml");
	    StudentService studentService = ctx.getBean(StudentServiceImp.class);
	    studentService.printStudentInfo();
	}
}

4、自动装配的歧义性

其实经过测试,使用 @Autowired 注解会存在歧义性,问题来源于如果两个StudentService 接口有两个实现类,Spring IoC 就会不知所措,不知道究竟该引入哪一个 Bean:就是测试用例中的这一句:

       StudentService studentService = ctx.getBean(StudentServiceImp.class);

Spring IoC 最底层的容器接口——BeanFactory 的定义,它存在一个按照类型获取 Bean 的方法,显然通过 StudentService .class 作为参数无法判断使用哪个类实例进行返回,这就是自动装配的歧义性。

为了消除歧义性,Spring 提供了两个注解:

  • @Primary 注解:代表首要的,当 Spring IoC 检测到有多个相同类型的 Bean 资源的时候,会优先注入使用该注解的类。该注解只是解决了首要的问题,但是并没有选择性的问题。
  • @Qualifier 注解:上面所谈及的歧义性,一个重要的原因是 Spring 在寻找依赖注入的时候是按照类型注入引起的。除了按类型查找 Bean,Spring IoC 容器最底层的接口 BeanFactory 还提供了按名字查找的方法,如果按照名字来查找和注入就能消除歧义性。

所有我们只需要在使用 @Autowired结合@Qualifier一起使用就能解决这个问题

@Component("studentService")
public class StudentServiceImp implements StudentService{
    @Autowired
    @Qualified("studentService")//studentService对应该具体实现类的id
    private Student student = null;
	public void printStudentInfo() {				
	     System.out.println("学生的 id 为:" + student.getId());
	     System.out.println("学生的 name 为:" + student.getName());
	}
}

5、使用@Bean 装配 Bean

  @Component 注解来装配 Bean ,并且只能注解在类上,不能注解在方法上。这时候,Spring提供了 @Bean 注解,注解到方法之上,使其成为 Spring 中返回对象为 Spring 的 Bean 资源。

使用示例:

  • 注意: @Configuration 注解相当于 XML 文件的根元素,必须要,有了才能解析其中的 @Bean 注解
package pojo;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class BeanTester {

    @Bean(name = "testBean")
    public String test() {
        String str = "测试@Bean注解";
        return str;
    }
}

测试:结果--->测试@Bean注解

// 在 pojo 包下扫描
ApplicationContext context = new AnnotationConfigApplicationContext("pojo");
// 因为这里获取到的 Bean 就是 String 类型所以直接输出
System.out.println(context.getBean("testBean"));

看看@Bean:

@Bean 的配置项中包含 4 个配置项:

  • name: 是一个字符串数组,允许配置多个 BeanName
  • autowire: 标志是否是一个引用的 Bean 对象,默认值是 Autowire.NO
  • initMethod: 自定义初始化方法
  • destroyMethod: 自定义销毁方法

Bean 的作用域

      在默认的情况下,Spring IoC 容器只会对一个 Bean 创建一个实例,但有时候,我们希望能够通过 Spring IoC 容器获取多个实例,我们可以通过 @Scope 注解或者 <bean> 元素中的 scope 属性来设置,例如:

// XML 中设置作用域
<bean id="" class="" scope="prototype" />
// 使用注解设置作用域
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)

Spring 提供了 5 种作用域,它会根据情况来决定是否生成新的对象:

                         

     在开发中主要使用 scope="singleton"scope="prototype"对于MVC中的Action使用prototype类型,其他使用singleton,Spring容器会管理 Action 对象的创建,此时把 Action 的作用域设置为 prototype。

5、其他注解:@Profile(“dev:用于开发”、"test:用于测试”)、@PropertySource(加载配置文件)、@Conditional(条件化转配Bean)

五、Spring 表达式语言简要说明

Spring 还提供了更灵活的注入方式,那就是 Spring 表达式,实际上 Spring EL 远比以上注入方式都要强大,它拥有很多功能:

  • 使用 Bean 的 id 来引用 Bean
  • 调用指定对象的方法和访问对象的属性
  • 进行运算
  • 提供正则表达式进行匹配
  • 集合配置

介绍下Spring EL的相关类。首先是ExpressionParser接口,它是一个表达式的解析接口,Spring提供了很多它的实现类。

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component("customerBean")
public class Customer {

    @Value("#{itemBean}")
    private Item item;
    
    @Value("#{itemBean.name}")
    private String itemName;
    
  //getter and setter...
}

1、结合@Value注解给属性赋值;

2、也可以调用方法:

@Value("#{'lei'.toUpperCase()}")
private String name;

3、做运算:

@Value("#{1 == 1}") //true
private boolean testEqual;

还有其他许多功能,实际应用再去做了解。

参看博客:https://www.cnblogs.com/wmyskxz/p/8830632.html

书籍:《Java EE 互联网轻量级框架整合开发》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值