[spring]03_装配Bean

简介:

3.1 JavaBean


3.1.1 JavaBean 是什么

JavaBean 是一种JAVA语言写成的可重用组件。

为写成JavaBean,类必须是具体的和公共的,并且具有无参数的构造器。

JavaBean 通过提供符合一致性设计模式的公共方法将内部域暴露成员属性。


以下是一个简单的JavaBean类。

定义一个Person类,有 name 和 age 两个属性,以及这两个属性的 get、set 方法。 

复制代码
package com.demo.web.controllers;

public  class Person {
     private String name = "zhangsan";
     private  int age = 28;

     public Person() {
    }
    
     public Person(String name,  int age) {
         this.name = name;
         this.age = age;
    }

     public String getName() {
         return name;
    }

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

     public  int getAge() {
         return age;
    }

     public  void setAge( int age) {
         this.age = age;
    }
}
复制代码


3.1.2 JavaBean 特点

  • JavaBean 是一个 public 类型的类
  • JavaBean 含无参数的构造函数
  • JavaBean 提供 set 和 get 方法 

 

3.1.3 JavaBean的生命周期

传统JavaBean生命周期

传统的Java应用,Bean的生命周期很简单。

使用new进行实例化,然后该Bean就可以使用了。程序结束后,Java会自动进行垃圾回收。

 

Spring容器中的JavaBean生命周期

在Spring容器中的Bean的生命周期要复杂多了,步骤如下:

(1)Spring对Bean进行实例化。

(2)Spring将值和Bean的引用注入进Bean对应的属性中。

(3)如果 Bean 实现了 BeanNameAware 接口,Spring将 Bean 的ID传递给 setBeanName() 接口方法。

(4)如果 Bean 实现了 BeanFactoryAware 接口,Spring将调用 setBeanFactory() 接口方法,将BeanFactory 容器实例传入。

(5)如果 Bean 实现了 ApplicationContextAware 接口,Spring将调用 setApplicationContext() 接口方法,将应用上下文的引用传入。

(6)如果 Bean 实现了 BeanPostProcessor 接口,Spring将调用它们的 post-ProcessBeforeInitialization接口方法。

(7)如果 Bean 实现了 InitializingBean 接口,Spring将调用它们的 afterPropertiesSet 接口方法。类似地,如果 Bean 使用 init-method 声明了初始化方法,该方法也会被调用。

(8) 如果 Bean 实现了 BeanPostProcessor接口,Spring将调用它们的 post-ProcessAfterInitialization接口方法。

(9)此时此刻,Bean 已经准备就绪,可以被应用程序是用来,它们将一直驻留在应用上下文中,直到该应用上下文被销毁。

(10)如果 Bean 实现了 DisposableBean 接口,Spring将调用它的 destroy() 接口方法。同样,如果Bean使用 destroy-method 声明了销毁方法,该方法也会被调用。
 

3.2 声明Bean


创建应用对象之间协作关系的行为通常被称为装配,这也是依赖注入的本质。

Spring是一个基于容器的框架。但是如果没有配置Spring,那它就是一个空容器,当然也毫无用处。

所以需要配置Spring,以告诉容器需要加载哪些Bean和如何装配这些Bean。

从Spring3.0开始,Spring容器提供了两种配置Bean的方式。

  • 使用XML文件作为配置文件
  • 基于Java注解的配置方式 

注:本文先不介绍注解的配置方式,而是重点介绍传统的XML配置方式。

 

3.2.1 创建Spring配置

以下是一个典型的XML配置文件(一般为<servlet名>-servlet.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-3.0.xsd"
>
     <!--  Bean declarations go here  -->
</ beans >
复制代码

 <beans> 元素内,可以放置所有的Spring配置信息。

实际上,beans这个标签是 Spring的一种命名空间。

Spring框架自带了10个命名空间,如下表所示:

命名空间

用途

aop

为声明切面以及将@AspectJ注解的类代理为Spring切面提供了配置元素

beans

支持声明Bean和装配Bean,是Spring最核心也是最原始的命名空间

context

为配置Spring应用上下文提供了配置元素,包括自动检测盒自动装配Bean、注入非Spring直接管理的对象

jee

提供了与Java EE API的集成,例如JNDIEJB

jms

为声明消息驱动的POJO提供了配置元素

lang

支持配置由GroovyJrubyBeanShell等脚本是实现的Bean

mvc

启用SpringMVC的能力,例如面向注解的控制器、试图控制器和拦截器

oxm

支持Spring的对象到XML映射配置

tx

提供声明式事务配置

util

提供各种各样的工具类元素,包括把集合配置为Bean、支持属性占位符元素

除了Spring框架自带的命名空间,Spring Portfolio的许多成员,例如Spring security、Spring Web Flow和Spring Dynamic Modules,同样提供了他们自己的命名空间配置。


3.2.2 声明JavaBean

以上文提到的Person 类为例,定义了JavaBean后,还需要在xml文件中声明,形式如下:

< bean  id ="person"  class ="com.demo.web.controllers.Person" />
这个标签的意思就是,创建一个  com.demo.web.controllers.Person 类的对象person。

当Spring容器加载这个Bean的时候,会使用默认构造器来实例化person对象,相当于:

com.demo.web.controllers.Person person =  new com.demo.web.controllers.Person();
备注实际上,Spring是使用反射机制来创建Bean的。


3.2.3 构造器注入

很多应用场景下,我们希望在初始化实例时,传入关键性的参数,这就需要带参数的构造函数了。

为了解决这个问题,在构造Bean的时候,可以使用 <constructor-arg> 元素来指定构造器参数。

< bean  id ="person"  class ="com.demo.web.controllers.Person" >
     < constructor-arg  value ="lisi"   />
     < constructor-arg  value ="28"   />
</ bean >

如果不使用这个标签,spring将使用默认构造函数。

以上参数都是直接传入值,如果想传入对象引用,那要怎么做呢?

请参考下面的例子:

首先定义一个Car类

复制代码
package com.demo.web.controllers;

public  class Car {
     private String model;
    
     public Car() {
    }
    
     public Car(String model) {
         this.model = model;
    }
    
     public  void run() {
        System.out.println(model + " 正在行驶");
    }
}
复制代码

再定义一个Driver类

复制代码
package com.demo.web.controllers;

public  class Driver {
     private Car car;
    
     public Driver() {
    }

     public Driver(Car car) {
         this.car = car;
    }
    
     public  void drive() {
        car.run();
    }
}
复制代码

接下来,我们可以在XML中配置一个Car的变量

< bean  id ="car"  class ="com.demo.web.controllers.Car" >
     < constructor-arg  value ="宝马5系"   />
</ bean >

有了Car的变量car,我们就可以在声明Driver变量时,使用 <constructor-arg> 标签引用它。

< bean  id ="driver"  class ="com.demo.web.controllers.Driver" >
     < constructor-arg  ref ="car"   />
</ bean >

 

3.2.4 通过工厂方法创建Bean

有时候一个类并没有public型的构造方法(典型的如单例模式里的类),对于这种情况如何在spring中实例化呢?

这时候静态工厂方法是实例化对象的唯一方法。Spring支持通过 <bean> 元素的 factory-method 属性来装配工厂创建的Bean。

复制代码
package com.demo.web.controllers;

public  class Singleton {
     static Singleton instance =  new Singleton();

     private Singleton() {
    }

     public  static Singleton getInstance() {
         return instance;
    }
}
复制代码

为了在Spring中将Singleton配置为Bean,可以按照下面的方式来配置

< bean  id ="instance"  class ="com.demo.web.controllers.Singleton"
        factory-method
="getInstance"   />

 

3.2.5 Bean的作用域

所有的Spring Bean默认都是单例。当容器分配一个Bean时,它总是返回Bean的同一个实例。

但有时我们需要每次请求时都获得唯一的Bean实例,如何做到呢?

当在Spring中配置 <bean> 元素时,我们可以为Bean声明一个作用域。为了让Spring在每次请求时都为Bean产生一个新的实例,我们只需要配置Bean的scope属性为 prototype即可。

< bean  id ="person"  class ="com.demo.web.controllers.Person"  scope ="prototype"   />

除了prototype,Spring还提供了其他几个作用域选项,如下:

作用域

定义

singleton

在每一个Spring容器中,一个Bean定义只有一个对象实例(默认)

prototype

运行Bean的定义可以被实例化任意次(每次调用都创建一个实例)

request

在一次HTTP请求中,每个Bean定义对应一个实例,该作用域仅在基于WebSpring上下文(例如Spring MVC)中才有效

session

在一个HTTP Session中,每个Bean定义对应一个实例,该作用域仅在基于WebSpring上下文(例如Spring MVC)中才有效

global-session

在一个全局HTTP Session中,每个Bean定义对应一个实例,该作用域仅在Portlet上下文中才有效

注: S pring的单例Bean只能保证在每个应用上下文中只有一个Bean的实 例。 


3.2.6 初始化和销毁Bean

当实例化一个Bean时,可能需要执行一些初始化操作来确保该Bean处于可用状态。同样地,当不再需要Bean,将其从容器中移除时,我们可能还需要按 顺序执行一些清除工作。为了满足初始化和销毁Bean的需求,Spring提供了Bean生命周期的钩子方法。

为Bean定义初始化和销毁操作,只需要使用 init-method 和 destroy-method 参数来配置 <bean> 元素。 init-method 属性指定了在初始化 Bean 时要调用的方法。类似地,destroy-method 属性指定了 Bean 从容器移除之前要调用的方法。 

假设,为一个灯泡的功能写一个类。

复制代码
package com.demo.web.controllers;

public  class Light {
     public  void turnOn() {
         //  ...
    }

     public  void turnOff() {
         //  ...
    }
}
复制代码

接着,我们在XML中做如下配置

< bean  id ="light"  class ="com.demo.web.controllers.Light"
        init-method
="turnOn"  destroy-method ="turnOff"   />

这样,就能保证让灯泡类在点亮之前调用turnOn(),结束时调用turnOff()。


3.3 注入Bean属性


通常,JavaBean中的属性都是私有的,同时提供一组get、set方法。 

(1)注入简单值

在Spring中,除了用前面介绍的构造器注入方式,还可以使用 <property> 元素配置 Bean 的属性。 

还是以Person类为例

< bean  id ="person"  class ="com.demo.web.controllers.Person" >
     < property  name ="name"  value ="wangwu"   />
     < property  name ="age"  value ="30"   />
</ bean >

 

(2)引用其他Bean

还记得我们在构造器注入部分提到的Driver和Car类的例子吗,如果使用property的方式,则按如下方式表达

复制代码
< bean  id ="baoma"  class ="com.demo.web.controllers.Car" >
     < constructor-arg  value ="宝马5系"   />
</ bean >

< bean  id ="driver"  class ="com.demo.web.controllers.Driver" >
     < property  name ="car"  ref ="baoma"   />
</ bean >
复制代码


(3)内部注入

内部注入是通过直接声明一个 <bean> 元素作为 <property>元素的子节点而定义的。

< bean  id ="driver"  class ="com.demo.web.controllers.Driver" >
     < property  name ="car" >
         < bean  class ="com.demo.web.controllers.Car"   />
     </ property >
</ bean >


(4)使用Sping的命名空间p装配属性

Spring还提供了一个命名空间p以简化<property>元素的装配方式。 

命名空间p的schema URI为 http://www.springframework.org/schema/p。

如果要使用命名空间p ,需要在Spring的XML配置中增加一段声明

复制代码
<? xml version="1.0" encoding="UTF-8" ?>
< beans  xmlns ="http://www.springframework.org/schema/beans"
    xmlns:p
="http://www.springframework.org/schema/p"  
    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-3.0.xsd"
>
复制代码

有了以上声明,我们就可以使用p:作为<bean>元素所有属性的前缀来装配Bean的属性。

用p:重新定义上节Driver类的配置, 如下:

复制代码
< bean  id ="baoma"  class ="com.demo.web.controllers.Car" >
     < constructor-arg  value ="宝马5系"   />
</ bean >

< bean  id ="driver"  class ="com.demo.web.controllers.Driver"
    p:car-ref
="baoma" />
复制代码
p:car-ref ="baoma" 表示要装配car 属性,-ref后缀表示这是一个引用而不是字面值。"baoma"是这个属性的引用对象。


3.3.1 装配集合

Spring不仅可以装配单个值,也可以装配集合。

Spring提供4种类型的集合配置元素。

集合元素

用途

对应实际数据类型

<list>

装配list类型的值,允许重复

数组或java.util.Collection

<set>

装配set类型的值,不允许重复

<map>

装配map类型的值,名称和值可以是任意类型

java.util.Map

<props>

装配properties类型的值,名称和值必须都是String

java.util.Properties

 

(1)<list> 和 <set>

< list  < set > 都可以用来装配类型为 java.util.Collection 的任意实现或数组的属性。

两者的区别在于:list 允许成员重复; set 必须保证成员不重复。

需要注意的是,不是说如果属性是java.util.Set 类型,则用户必须用 <set> 装配。

定义一个 instruments 属性,它是一个Collection,保存各种乐器 

复制代码
< property  name ="instruments" >
     < list >
         < ref  bean ="guitar"   />
         < ref  bean ="piano"   />
         < ref  bean ="piano"   />
     </ list >
</ property >

< property  name ="instruments" >
     < set >
         < ref  bean ="guitar"   />
         < ref  bean ="piano"   />
         < ref  bean ="piano"   /> <!--  自动忽略重复的属性  -->
     </ set >
</ property >
复制代码

 

(2)<map>

< map > 元素声明了一个  java.util.Map 类型的值。每个  < entry > 元素定义 Map 的一个成员。
< entry > 元素由一个键和一个值组成,键和值可以是简单类型,也可以是其他Bean的引用。

属性

用途

key

指定mapentry的键为String

key-ref

指定mapentry的键为Spring上下文中其他Bean的引用

value

指定mapentry的值为String

value-ref

指定mapentry的值为Spring上下文中其他Bean的引用

复制代码
< property  name ="instruments" >
     < map >
         < entry  key ="GUITAR"  value-ref ="guitar" >
         < entry  key ="PIANO"  value-ref ="piano" >
     </ map >
</ property >
复制代码

 

(3)<prop>

如 果  Map  的每一个  entry  的键和值都为  String  类型时,可以考虑使用  java.util.Properties  代替 Map。Properties 类提供了和 Map 大致相同的功能,但是它限定了键和值必须为 String 类型。
< props > 元素构建了一个 java.util.Properties 值,这个 Properties 的每一个成员由  < prop > 定义。

复制代码
< property  name ="instruments" >
     < props >
         < prop  key ="GUITAR" >guitar sound </ prop >
         < prop  key ="PIANO" >piano sound </ prop >
     </ props >
</ property >
复制代码


3.4 使用表达式装配


Spring 3引入了Spring表达式语言(Spring Expression Lanuage, SpEL)。

它通过运行期执行的表达式将值装配到 Bean 的属性或构造器参数中。 

 

3.4.1 SpEL 的基本原理

SpEL 表达式的首要目标是通过计算获得某个值。最简单的SpEL求值或许是对字面值、Bean的属性或某个类的常量进行求值。 

字面值

< property > 元素的 value 属性中使用 #{} 界定符把这个值装配到 Bean 的属性中

< property  name ="count"  value ="#{5}"   />
此外,也可以与非SpEL 表达式的值混用
< property  name ="count"  value ="The value is #{5}"   />


引用Bean、Properties 和方法

SpEL 表达式可以通过 ID 引用其他 Bean。

< property  name ="fruit"  value ="#{apple}"   />

这和以下语句的功能等价

< property  name ="fruit"  ref ="apple"   />

除了直接引用其他Bean,也可以引用 Bean 对象的属性和方法

< property  name ="song"  value ="#{singer.song}"   />
< property  name ="song"  value ="#{singer.selectSong().toUpperCase()}"   />

需要注意的是,singer.selectSong().toUpperCase()存在一个问题,如果selectSong()方法返回的是null, 那么SpEL表达式求值时会抛出一个 NullPointerException 异常。

为了避免这种情况,可以使用null-safe存取器(?.)

< property  name ="song"  value ="#{singer.selectSong()?.toUpperCase()}"   />

使用 ?. 代替 . 来访问 toUpperCase() 方法。?. 可以确保只有当 selectSong() 不为 null 时才去调用 toUpperCase() 方法。


操作类

现在,我们了解了在 SpE L中,如何去调用 Bean 对象,以及对象的属性和方法。

但是,如何去访问类的静态方法或常量引用呢?

在 SpEL 中,使用 T() 运算符去调用类作用域的方法和常量。

以下演示了如何调用 java.lang.Math 类中的静态方法和属性。 

< property  name ="multiplier"  value ="#{T{java.lang.Math}.PI}"   />
< property  name ="multiplier"  value ="#{T{java.lang.Math}.random}"   />


在 SpEL 值上执行运算操作

SpEL提供了几种运算符,这些运算符可以用在SpEL表达式中的值上。

运算符类型

运算符

算术运算

+-*/%^

关系运算

<>==<=>=ltgteqlege

逻辑运算

andornot|

条件运算

?: (ternary)?: (Elvis)

正则表达式

matches


3.4.2 在 SpEL 中筛选集合 

假设针对前面的Person类,我们定义一个List集合,如下: 

复制代码
< util:list  id ="persons" >
     < bean  class ="com.demo.web.controllers.Person"  p:name ="zhangsan"  p:age ="17" />
     < bean  class ="com.demo.web.controllers.Person"  p:name ="lisi"  p:age ="24" />
     < bean  class ="com.demo.web.controllers.Person"  p:name ="wangwu"  p:age ="30" />
     < bean  class ="com.demo.web.controllers.Person"  p:name ="zhaoliu"  p:age ="16" />
     < bean  class ="com.demo.web.controllers.Person"  p:name ="liuqi"  p:age ="23" />
</ util:list >
复制代码

访问集合成员

可以使用 [] 运算符来访问集合成员

< property  name ="choosePerson"  value ="#{persons[T{java.lang.Math}.random() * persons.size()]]}"   />

以上表示,随机选取一个人。

也可以按下面方式选取

< property  name ="choosePerson"  value ="#{persons['zhangsan']}"   /> <!--  选取集合中叫张三的人  -->
< property  name ="choosePerson"  value ="#{persons[2]}"   /> <!--  选取集合中第二个人  -->


查询集合成员

如果想要在persons集合中查询年龄大于18岁的人。 

在SpEL中,只需使用一个查询运算符(.?[]) 就可以简单做到,如下所示:

< property  name ="adults"  value ="#{persons.?[age gt 18]}"   />

查询运算符会创建一个新的集合,集合中只存放符合括号中表达式的成员。 

SpEL 还提供两种运算符:.^[] 和 .$[],从集合中查询出第一个匹配项和最后一个匹配项。 


投影集合

在SpEL中, 提供了投影运算符(.![])将集合中每个成员的特定属性放入一个新的集合中。

< property  name ="personNames"  value ="#{persons.![name]}"   />

以上,将所有人的名字取出来,建立一个新的集合personNames。

 

3.5 JavaBean的一个简单应用实例


以下 以 [Spring]01_ 环境配置 中的 HelloWorld 工程为基础,演示一下 JavaBean 的简单使用方法。

打开 HelloWorld 工程。

(1)新建一个 java 文件,名为 Person.java,完整内容如下:

  View Code

(2)修改 index.jsp 文件,完整内容如下:

  View Code

(3)运行

结果如下:

  View Code

本文转自静默虚空博客园博客,原文链接:http://www.cnblogs.com/jingmoxukong/p/4532680.html,如需转载请自行联系原作者

相关文章
|
24天前
|
缓存 Java Spring
Spring 框架中 Bean 的生命周期
Spring 框架中 Bean 的生命周期
32 1
|
1月前
|
XML Java 开发者
Spring Boot中的bean注入方式和原理
Spring Boot中的bean注入方式和原理
61 0
|
1月前
|
XML 缓存 Java
Spring源码之 Bean 的循环依赖
循环依赖是 Spring 中经典问题之一,那么到底什么是循环依赖?简单说就是对象之间相互引用, 如下图所示: 代码层面上很好理解,在 bean 创建过程中 class A 和 class B 又经历了怎样的过程呢? 可以看出形成了一个闭环,如果想解决这个问题,那么在属性填充时要保证不二次创建 A对象 的步骤,也就是必须保证从容器中能够直接获取到 B。 一、复现循环依赖问题 Spring 中默认允许循环依赖的存在,但在 Spring Boot 2.6.x 版本开始默认禁用了循环依赖 1. 基于xml复现循环依赖 定义实体 Bean java复制代码public class A {
|
1月前
|
存储 NoSQL Java
Spring Boot统计一个Bean中方法的调用次数
Spring Boot统计一个Bean中方法的调用次数
35 1
|
2月前
|
Java 索引 Spring
spring创建bean的过程
spring创建bean的过程
|
1天前
|
XML 人工智能 Java
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
|
9天前
|
Java 数据库连接 开发者
浅谈Spring的Bean生命周期
浅谈Spring的Bean生命周期
17 1
|
13天前
|
XML Java 数据格式
Bean工厂探秘:解析Spring底层工厂体系BeanFactory的神奇之道
Bean工厂探秘:解析Spring底层工厂体系BeanFactory的神奇之道
19 0
Bean工厂探秘:解析Spring底层工厂体系BeanFactory的神奇之道
|
13天前
|
Java 关系型数据库 MySQL
高级对象装配:解析Spring创建复杂对象的秘诀
高级对象装配:解析Spring创建复杂对象的秘诀
27 0
高级对象装配:解析Spring创建复杂对象的秘诀
|
24天前
|
XML Java 程序员
作为Java程序员还不知道Spring中Bean创建过程和作用?
作为Java程序员还不知道Spring中Bean创建过程和作用?
14 0