您的位置:首页 > 编程语言 > Java开发

在IoC容器中装配Bean(精通Spring+4.x++企业应用开发实战 四)

2017-09-05 13:42 1011 查看
本章的pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion>
<groupId>com.smart</groupId>
<artifactId>chapter5</artifactId>
<version>1.0</version>
<name>Spring4.x第五章实例</name>
<dependencies>

<!-- spring 依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>

<!-- asm/cglib依赖(spring依赖) -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>${cglib.version}</version>
<exclusions>
<exclusion>
<artifactId>asm</artifactId>
<groupId>org.ow2.asm</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${aspectj.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.version}</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>${commons-codec.version}</version>
</dependency>

<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>${groovy.version}</version>
</dependency>

<dependency><
4000
/span>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>${testng.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.7.2</version>
<configuration>
<parallel>methods</parallel>
<threadCount>10</threadCount>
<argLine>-Dfile.encoding=UTF-8</argLine>
<!-- <skip>true</skip> -->
<!-- <testFailureIgnore>true</testFailureIgnore> -->
</configuration>
</plugin>
</plugins>
</build>

<properties>
<file.encoding>UTF-8</file.encoding>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>

<spring.version>4.2.2.RELEASE</spring.version>
<groovy.version>2.3.6</groovy.version>
<mockito.version>1.10.19</mockito.version>
<testng.version>6.8.7</testng.version>
<asm.version>4.0</asm.version>
<cglib.version>3.0</cglib.version>
<aspectj.version>1.8.1</aspectj.version>
<aopalliance.version>1.0</aopalliance.version>
<commons-codec.version>1.9</commons-codec.version>
</properties>
</project>


Spring配置概述

Spring容器高层视图



Bean配置信息是Bean的元数据信息,由4方面组成



Spring容器,Bean配置信息,Bean实现类以及应用程序的关系



基于XML的配置

基于XML的配置,Spring采用Schema格式的配置方案为许多领域的问题提供了简化的配置方法,简化了配置工作

采取基于Schema的配置格式,文件头的声明会复杂一些,看一个示例:



在上面定义了3个命名空间

①默认命名空间:没有空间名,用于Spring Bean的定义

②xsi标准命名空间:这个命名空间用于为每个文档中的命名空间指定响应的Schema样式文件,是W3C定义的标准命名空间

③aop命名空间:这个命名空间是Spring配置AOP的命名空间,即一种自定义的命名空间

命名空间的定义分为两个步骤:

第一步指定命名空间的名称

需要指定命名空间的缩略名和全名,如

xmlns:aop="http://www.springframework.org/schema/aop"


aop为命名空间的别名,文档后面的元素可以通过命名空间的别名加以区分,如<aop:config/>

http://www.springframework.org/schema/aop为空间的全限定名,习惯上用文档发布机构的官方网站或相关网站目录作为全限定名

如果命名空间的别名为空,则表示该命名空间为文档默认命名空间。文档中无命名空间前缀的元素都属于默认命名空间,如<beans/>属于在①处定义的默认命名空间

第二步 指定命名空间在Schema文档格式文件的位置,用空格或回车换行分隔

为每个命名空间指定对应的Schema文档格式的定义文件(图中的④),语法如下:

<命名空间 1> <命名空间 1 Schema文件>  <命名空间 2> <命名空间 2 Schema文件>


使用空格或回车分隔

在xsi:schemaLocation中,如

xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd[/code] 
指定命名空间的Schema文件地址有两个用途:

XML解析器可以获取Schema文件并对文档进行格式合法性验证

在开发环境下,IDE可以引用Schema文件对文档编辑提供诱导功能(自动补全功能)

对Schema文件的用途进行说明







除支持XML配置方式外,Spring还支持基于注解,java类以及Groovy的配置方式

Bean基本配置

装配一个Bean



id为这个Bean 的名称,通过容器的getBean(“Foo”)可以获取对应的Bean,在容器中祈祷定位查找的作用,class属性指定了Bean对应的实现类

Bean的命名

id在IoC容器中必须是唯一的,满足XML对id的命名贵方:必须以字母开始,后面可以是字母,数字,连字符。下划线,句号,冒号等完整结束的符号,逗号和空格这些非完整结束符是非法的

如果想用特殊字符进行命名,可以使用<bean>的name属性。

id和name都可以指定多个名字,名字之间可以逗号,分号或空格分隔

不可以出现相同id的<bean>,可以出现相同name<bean>.有多个name相同的<bean>,通过getBean()获取Bean时,将返回后面声明的那个Bean,因为后面那个覆盖了前面的。所以应使用id

如果id和name两个属性都为指定,如<bean class=”com.smart.Car”>,Spring将自动将全限定类名作为Bean的名称

如果存在多个实现类相同的匿名<bean>



在实际中,使用id是最好的

依赖注入

Spring支持属性注入和构造函数注入,还支持工厂方法注入方式

属性注入

属性注入方式具有可选择性和灵活性高的有点,在实际应用中最常用

实例

属性注入要求Bean提供一个默认的构造函数,并为需要注入的属性提供的Setter方法。

Spring先调用Bean 的默认构造函数实例化Bean对象,然后通过反射的方式调用Setter方式注入属性值

public class Car {
private String brand;
private String color;
private int maxSpeed;
public void setBrand(String brand) {
this.brand = brand;
}
public void setColor(String color) {
this.color = color;
}
public void setMaxSpeed(int maxSpeed) {
this.maxSpeed = maxSpeed;
}
//...
}


在Spring配置文件中对Car进行属性注入的配置片段:





<beans ...
xmlns:p="http://www.springframework.org/schema/p"
...>
<bean id="car" class="com.smart.Car"
p:color="黑色"
p:brand="红旗CA72"
p:maxSpeed="200"
/>
...
</beans>


JavaBean关于属性命名的特殊规范

一般Java的属性名都以小写字母开头,但JavaBean也允许以大写字母开头的属性变量名,但必须满足”前两个字母要么全部大写,要么全部小写”的要求

为了避免错误,可以将像QQ,MSN,ID等专业术语在Java中一律调整为小写,保证命名的统一性

构造函数注入

保证一些必要的属性在Bean实例化时就得到设置,确保Bean实例化后就可以使用

按类型匹配入参



在配置文件中使用构造函数注入的配置方式装配这个car Bean



按索引匹配入参

如果Car构造函数有两个类型相同的入参,仅通过type就无法确定对应关系,这时需要通过入参索引来进行确定,如:



Spring无法确定对应的是brand还是corp,因此通过显式指定参数的索引



联合使用类型和索引匹配入参





通过自身类型反射匹配入参

如果Bean构造函数入参的类型是可辨别的(非基础数据类型且入参类型各异),由于Java反射机制可以获取构造函数入参的类型,即使构造函数注入的配置不提供类型和索引的信息,Spring依然可以正确完成构造函数的注入工作





循环依赖问题

Spring容器对构造函数配置的Bean进行实例化有一个前提,即Bean构造函数入参引用的对象必须已经准备就绪。

如果两个Bean都采用构造函数注入,而且都通过构造函数入参引用对方,会有类似死锁的循环依赖问题







这时就需要使用属性注入

工厂方法注入

非静态工厂方法

非静态工厂方法,必须实例化工厂类后才能调用工厂方法

工厂类负责创建一个或多个目标类实例,工厂类方法一般以接口或抽象类变量的形式返回目标类实例

public class CarFactory {
//创建Car的工厂方法
public Car createTeslaCar(){
Car car=new Car();
car.setBrand("特斯拉modelS");
return car;
}
}


工厂方法的注入



静态(static)工厂方法

public class CarFactory {
public staticCar createTeslaCar(){ //工厂类方法是静态的
...
}
}


当使用静态工厂类型的方法后,用户就无须在配置文件中定义工厂类的Bean,只需:



注入参数详解

字面值

字面值一般指可用字符串表示的值,这些值可以通过<value>元素标签进行注入。

默认情况下,基本数据类型及其封装类,String等类型都可以采取字面值注入的方式。

Spring容器在内部为字面值提供了编辑器,可将以字符串表示的字面值转换为内部变量的相应类型。允许注册自定义的编辑器



①处的brand属性值包含一个特殊的XML符号,<![CDATA[]]>的作用是让XML解析器将标签中的字符串当做普通的文本对待,以防特殊字符对XML格式造成破坏。



引用其他Bean



<ref>元素可以通过以下3个属性引用容器中的其他Bean





内部Bean

如果carBean只被bossBean引用,而不被容器中任何其他的Bean引用,则可以将car以内部Bean的方式注入Boss中



null值

为属性设置一个null的注入值,必须使用<null />元素标签

<property name="brand"><null/></property>
<!--而不是-->
<property name="brand"><value></value></property>


级联属性

Spring支持级联属性的配置(类似与com.smart)

在定义Boss时直接为Car的属性提供注入值



按上面的配置,Sprign将调用Boss.getCar().setBrand(“吉利CT50”)方法进行属性注入操作

为car属性声明一个初始化对象



集合类型属性

List

为Boss添加一个List类型的favorites属性



对应的配置为



Set

类似,把<list>改为<set>即可

Map

jobs属性是Map类型的





假如某一Map元素的键和值都是对象,可以采用以下配置方式



Properties

Properties类型可以看做Map的特例。Map的键和值可以是任何类型的对象,而Properties属性的键和值只能是字符串

mails属性是Properties类型的



没有<value>子标签

集合合并

Spring支持集合合并的功能,允许子<bean>继承父<bean>的同名属性集合元素,并将子<bean>中配置的集合属性值和父<bean>中配置的同名属性值合并起来作为最终Bean的属性值



merge=”true”指示子<bean>和父<bean>中同名属性值进行合并,子Bean的favorites集合有5个元素。

通过util命名空间配置集合类型的Bean

如果希望配置一个集合类型的Bean,而非一个集合类型的属性,可以通过util命名空间进行配置,首先需要在Spring配置头文件中引入util命名空间的声明



然后配置一个List类型的Bean,可以通过list-class显式指定List的实现类



<util:list>支持value-type属性,指定集合中的值类型

配置一个Set类型的Bean,可以通过set-class显式指定Set的实现类



<util:set>支持value-type属性,指定集合中的值类型

配置一个Map类型的Bean,可以通过map-class显式指定Map的实现类



<util:map>支持key-type和value-type属性,指定Map的键和值类型

简化配置方式



使用简化的方式,无法使用<![CDATA[]]>处理XML特殊字符,只能使用XML转义序列对特殊字符进行转换



<ref>的简化形式对应于<ref bean=”xxx”>而<ref parent=”xxx”>和<ref local=”xxx”>没有对应的简化形式

使用p命名空间





因为p命名空间中的属性名是可变的,所以p命名空间没有对应的Schema定义文件,也就无须早xsi:schemaLocation中为p命名空间指定Schema定义文件

自动装配

<bean元素>提供了一个指定自动装配类型的属性:autowire=”<自动装配类型>”

Spring提供了4种自动装配类型



<beans>元素标签中的default-autowire属性可以配置全局自动匹配,default-autowire属性的默认值为no,表示不启动自动装配

方法注入

在非Web应用的环境下,Bean只有singleton和prototype两种作用域。

如果往singleton的Boss中注入prototype的Car,并希望每次调用boss Bean的getCar()方法时都能够返回一个新的car Bean,使用传统的注入方式无法实现这样的要求。

因为singleton的Bean注入关联Bean的动作仅有一次,虽然car Bean 的作用域是prototype类型,但Boss通过getCar()方法返回的对象还是最开始注入的那个car Bean



如果希望每次调用getCar()方法都返回一个新的car Beaned实例,一种方法是让Boss实现BeanFactoryAware接口,且能够访问容器的引用,这样Boss的getCar()方法就可以采用以下方式来达到目的:



lookup方法注入

Spring IoC容器拥有复写Bean方法的能力,这功能归功于CGLib类包。

CGLib可以在运行期动态操作Class字节码,为Bean动态创建子类或实现类。

现在声明一个MagicBoss接口,并声明一个getCar()的接口方法。

public interface MagicBoss{
Car getCar();
}


通过配置为该接口提供动态的实现,让getCar()接口方法每次都返回新的carBean



通过lookup-method元素标签为MagicBoss的getCar()提供动态实现,返回prototype类型的car Bean,这样Spring将在运行期为MagicBoss接口提供动态实现,效果等同于:



每次调用MagicBoss的getCar()方法都会从容器中获取car Bean,由于car Bean的作用域是prototype,所以每次都返回新的car实例。

looup方法注入,一般在希望通过一个singleton Bean获取一个prototype Bean时使用

方法替换

用于替换别的Bean必须实现MethodReplacer接口,Spring利用该接口的方法替换目标Bean的方法。

Boss1返回一台凯迪拉克

Boss2实现了Spring 的MethodReplacer接口,在接口方法reimplement()中,返回一辆特斯拉



当从容器中返回boss1 Bean并调用getCar()方法时,将返回一辆特斯拉

<bean>之间的关系

继承

OOP(Object Oriented Programming,面向对象编程)思想告诉我们,如果多个类拥有相同的方法和属性,则可以引入一个父类,在父类中定义这些类共同的方法和属性,以消除重复的代码。

如果多个<bean>之间存在相同的配置信息,Spring允许定义个父<bean>,子<bean>将自动继承父<bean>的配置信息





父<bean>主要功能是简化子<bean>的配置,一般声明为”abstract=true”,表示这个<bean>不实例化为一个对应的Bean,否则IoC容器会实例化此Bean

依赖

一般,可以使用<ref>元素标签建立对其他Bean 的依赖关系,Spring辅助管理这些Bean的关系

但有时这种Bean之间的依赖关系并不明显

例如,论坛拥有很多系统参数(如会话Session过期时间,缓存更新时间等),这些系统参数用于控制系统的运行逻辑,用一个类表示这些系统参数



提供一个管理后台,在管理后台可以调整这些系统参数并保存在后台数据库中,在系统启动时,初始化程序从数据库后台加载这些系统参数的配置值以覆盖默认值



假设有一个缓存刷新管理器,需要根据系统参数SystemSettings.REFRESH_CYCLE创建缓存刷新定时任务





CacheManager依赖与SystemSettings,而SystemSettings的值由SysInit负责初始化,虽然CacheManager不直接依赖于SysInit,但逻辑上看CacheManager希望在SysInit加载并完成系统参数设置后再启动,以避免调用不到真实的系统参数值。

Spring允许通过depends-on属性显式指定Bean前置依赖的Bean,前置依赖的Bean会在本Bean实例化前创建好



如果前置依赖多个Bean,可以通过逗号,空格或分号分隔

引用

依赖表示一个依赖者(程序集或功能或模块)需要一个被依赖者(程序集或功能或模块),两者建立了松散地绑定关系…依赖者可以通过被依赖者实现一些功能,缺少被依赖者也可以运行只不过不能实现这部分功能…

引用一般特指程序集,表示一个程序集须要另一个程序集,两者建立了紧密地绑定关系…缺少被引用者不能运行甚至连编译都不能…引用其实就是一种显式依赖关系…

假设一个<bean>要引用另一个<bean>的id属性值,可如下配置



通过②的方式以字面值的形式进行设置,但两者之间并没有建立引用关系。

一般在一个Bean中引用另一个Bean的id是希望在运行期通过getBean(beanName)方法获取对应的Bean。Spring为此可以提供了一个<idref>标签,通过它引用另一个<bean>的名字,在容器启动时,Spring负责检查引用关系的正确性,可以提前发现错误

所以,下面是推荐的优化方案:



如果引用的Bean不存在,在Spring容器启动时,将会提示BeanDefinitionStoreException

如果引用者和被引用者的<bean>位于同一个XML配置文件中,则可以使用<idref local=”car”>这样的配置方式,IDE的XML分析器可以在开发期发现引用错误

整合多个配置文件

在启动Spring容器时,可以通过一个String数组指定多个XML配置文件

可以通过<import>将多个配置文件引入到一个文件中,进行配置文件的集成,这样,在启动Spring容器时,仅需指定这个合并好的配置文件即可。



假设在beans1.xml中配置了car1和car2的Bean,在①通过<import>的resource属性引入beans.xml,beans2.xml就拥有了完整的配置信息,Spring容器仅需要通过beans2.xml就可以加载所有的配置信息

如果一个配置文件a.xml定义的<bean>引用了另一个配置文件b.xml定义的<bean>,并不一定需要通过<import>引入b.xml,只需在启动Spring容器时,两个配置文件都在配置文件列表中。

实际中最好是加载整合的配置文件

Bean作用域



Spring还允许自动Bean的作用域,通过org.springframework.beans.factory.config.Scope接口定义新的作用,在通过org.springframework.beans.factory.config.CustomScopeConfigurer这个BeanFactoryPostProcessor注册自定义的Bean作用域

singleton作用域

在Spring IoC容器中仅存在一个Bean实例,Bean以单实例的方式存在

一般,无状态(或称变量)或状态不可变的类适合使用单例模式。在Spring中,队友所有的DAO类都可以采用单例模式,因为Spring利用AOP和LocalThread功能,对非线程安全的变量(或称状态)进行处理,使这些非线程安全的类变成了线程安全的类

Spring将Bean的默认作用域定为singleton,可通过scope属性定义作用域



指向同一个Bean



默认情况下,ApplicationContext容器在启动时,自动实例化所有singleton的Bean并缓存在容器中,虽耗费多点时间,但有两个好处:

①对Bean提前进行实例化操作会及早发现潜在的配置问题

②Bean以缓存的方式保存,当运行时调用该Bean时就无需在实例化了,提高了运行的效率

如果不希望在容器启动时提前实例化singleton的Bean,可以通过lazy-init属性进行控制

<bean id="boss1" class="com.smart.scope.Boss" p:car-ref="car" lazy-init="true">


lazy-init=”true”的Bean如果被其他需要提前实例化的Bean引用,Spring将忽略此设置提前实例化

prototype作用域

每次从容器中调用Bean时,都会返回一个新的实例,即每次调用getBean()时,相当于执行 new xxx()操作

如上面,将car的scope=”prototype”



默认情况下,Spring容器在启动时不实例化prototype的Bean。Spring容器将prototype的Bean交给调用者后,就不再管理其生命周期

与Web应用环境相关的Bean作用域

如果使用Spring 的WebApplicationContext,则可使用另外3种Bean的作用域:request,session和globalSession,在使用这些作用域之前,必须在Web容器中进行一些额外的配置

在Web容器中进行额外配置

在低版本的Web容器中(Servlet 2.3之前),可以使用HTTP请求过滤器RequestContextFilter进行配置



在高版本的Web容器中,利用HTTP请求监听器RequestContextListener进行配置



(上章)WebApplication初始化时,已经通过ContextLoaderListener(或ContextLoaderServlet)将Web容器与Spring容器进行了整合



ContextLoaderListener实现了ServletContextListener监听器接口,ServletContextListener只负责监听Web容器的启动和关闭事件

RequestContextListener实现了ServletRequestListener监听器接口,该监听器监听HTTP请求事件,Web服务器接受的每一个请求都会通知该监听器

request作用域

request作用域的Bean对应一个HTTP请求和生命周期,每次HTTP请求都会创建一个新的Bean

<bean name="car" class="com.smart.scope.Car" scope="request" />


每次HTTP请求调用car Bean时,Spring容器创建一个新的car Bean,请求处理完毕后,就销毁这个Bean

session作用域

<bean name="car" class="com.smart.scope.Car" scope="session" />


car Bean的作用域横跨整个HTTP Session,Session中的所有HTTP请求都共享同一个car Bean。当HTTP Session结束后,实例才被销毁

globalSession作用域

<bean name="car" class="com.smart.scope.Car" scope="globalSession" />


类似于session作用域,但仅在Portlet的Web应用中使用。

Portlet规范定义了全局Session的概念,它被组成Portlet Web应用的所有子Portlet共享。如果不在Portlet Web应用环境下,那么globalSession等价于session

作用域依赖问题

将Web相关作用域的Bean注入singleton或prototype的Bean中,如果没有进行额外配置,将失败。

这个时候,需要Srping AOP



car Bean是request作用域,它被singleton作用域的boss Bean引用,为了使boss能够从适当作用域中获取car Bean的引用,需要使用Spring AOP的语法为car Bean配置一个代理类,如②。

为了能够在配置文件中使用AOP的配置标签,需要在文档声明头中定义aop命名空间,如①。

当boss Bean在Web环境下调用car Bean时,Spring AOP将启动动态代理智能判断boss Bean位于哪个HTTP请求线程中,并从对应的HTTP请求线程域中获取对应的car Bean



boss Bean的作用域是singleton,在Spring容器中始终只有一个实例。car Bean作用域为request,每个调用car Bean的HTTP请求都会创建一个car Bean。Spring通过动态代理技术,能够让boss Bean引用到对应HTTP请求的car Bean

在配置文件中添加<aop:scoped-proxy/>后,注入boss Bean中的car Bean不是原来的car Bean,而是car Bean的动态代理对象。

这个动态代理是Car类的子类(或实现类,假设Car是接口),Spring在动态代理子类中加入一段逻辑,以判断当前的boss需要获得哪个HTTP请求相关的car Bean

Java只能对接口提供自动代理,要对类提供代理,需要在类路径中加入CGLib的类库,这是Spring将利用CGLib为类生成动态代理的子类

FactoryBean

Spring提供了一个FactoryBean工厂类接口,用户可以通过实现该工厂类接口定制实例化Bean的逻辑

FactoBean支持泛型,有3个接口方法:



当配置文件中<bean>的class属性配置的实现类是FactoryBean时,通过getBean()方法返回的不是FactoryBean本身,而是FactoryBean#getObject()方法所返回的对象,相当于FactoryBean#getObject()代理了getBean()方法

我们希望通过逗号分隔的方式一次性为Car的所有属性指定配置值,可以编写一个FactoryBean来实现



有了这个CarFactoryBean,可以在配置文件中使用自定义的配置方式配置Car Bean



当调用getBean(“car”),Spring通过反射机制发现CarFactoryBean实现了FactoryBean的接口,Spring就调用接口方法getObject()返回工厂类创建的对象。

如果希望获取CarFactoryBean的实例,需要在使用getBean(beanName)方式时显式地在beanName前加上”&”前缀,即getBean(“&car”);

基于注解的配置

使用注解定义Bean

Spring容器成功启动的三大要件分别是Bean定义信息,Bean实现类及Spring本身。

如果采用基于XML的配置,Bean定义信息和Bean实现类本身是分离的

如果采用基于注解的配置文件,Bean定义信息通过在Bean实现类上标注注解实现

下面是使用注解定义一个DAO的Bean

@Component("userDao")
public class UserDao{ ...
}


@Component注解在UserDao类声明处对类进行标注,可以被Spring容器识别,Spring容器自动将POJO(Plain Ordinary Java Object,简单的Java对象)转换为容器管理的Bean,等效于以下XML配置

<bean id="userDao" class="com.smart.anno.UserDao" />


Spring还提供了三个功能基本和@Component等效的注解,分别用于对DAO,Service和Web层的Controller进行注解

①@Repository(仓库):用于对DAO实现类进行标注

②@Service:用于对Service类进行标注

③@Controller:用于对Controller实现类进行标注

扫描注解定义的Bean

Spring提供了一个context命名空间,它提供了通过扫描类包以应用注解定义Bean的方式



在①处声明context命名空间,在 ②中即可通过context命名空间的component-scan的base-package属性指定一个需要扫描的基类包,Spring容器会扫描这个基类包里的所有类,并从类的注解信息中获取Bean的定义信息

如果仅希望扫描特定的类而非基类包下的所有类,可以使用resource-pattern属性过滤出特定的类

<context:component-scan base-package="com.smart" resource-pattern="anno/*.class"/>


默认情况下resource-pattern属性的值为”*/.class”,即基类包中所有的类。上面仅扫描类包anno子包中的类

仅需过滤类包中实现了XxxService接口的类或标注了某个特定注解的类时,通过<context:component-scan>的过滤子元素可以实现

<context:component-scan base-package="com.smart">
<context:include-filter type="regex"  expression="com\.smart\.anno.*"/>
<context:exclude-filter type="aspectj" expression="com.smart..*Controller+"/>
</context:component-scan>


<context:include-filter>表示要包含的目标类,而 <context:exclude-filter>表示要排除的目标类。一个<context:component-scan>下可以有若干个 <context:include-filter>和 <context:exclude-filter>元素

这两个过滤元素支持多种类型的过滤表达式



除custom类型外,aspectj的过滤表达式可以轻易实现其他类型所能表达的过滤规则

<context:component-scan>有一个use-default-filters属性,默认值为true,表示默认会对标注@Component,@Controller,@Service,@Reposity的Bean进行扫描

<context:component-scan>先根据<exclude-filter/>列出需要排除的黑名单,在通过<include-filter/>列出需要包含的白名单。

由于use-default-filters属性默认的作用,下面配置片段会扫描@Component,@Controller,@Service,@Reposity的Bean



即加不加<context:exclude-filter/>效果都是一样的,如果想仅扫描@Controller的Bean,use-defaul-filters属性设置为false

自动装配Bean

使用@Autowired进行自动注入

Spring通过@Autowired注解实现Bean的依赖注入



@Autowired默认按类型(byType)匹配的方式在容器中查找匹配的Bean,当有且只有一个匹配的Bean时,Spring将其注入@Autowired标注的变量中

使用@Autowired的required属性

如果容器中没有一个和标注变量类匹配的Bean,那么Spring容器启动时将报NoSuchBeanDefinitionException异常。如果希望Spring找不到匹配的Bean完成注入也不要抛出异常,可以使用@Autowired(required=false)进行标注。

默认情况下,@Autowired的required属性值为true,即必须找到匹配的Bean,否则报异常

使用@Qualifier指定注入Bean 的名称

如果容器中有一个以上匹配的Bean时,可以通过@Qualifier注解限定Bean的名称





假设容器有两个类型为UserDao的Bean,一个名为userDao,另一个名为otherUserDao,则①处会注入名为userDao的Bean

对类方法进行标注

@Autowired可以对类成员变量及方法的入参进行标注



如果一个方法拥有多个入参,则在默认情况下,Spring将自动选择匹配入参类型的Bean进行注入。Spring允许对方法入参标注@Qualifier以指定注入Bean的名称



UserDao的入参注入名为userDao的Bean,而LogDao的入参注入LogDao类型的Bean

一般情况下,在Spring容器中大部分Bean都是单实例,所以一般无需通过@Repository,@Service等注解的value属性为Bean指定名称,也无需使用@Qualifier注解按名称进行注入

虽然Spring支持在属性和方法上标注自动注入注解@Autowired,但在实际开发中建议采用在方法上标注@Autowired注解,这样更加“面向对象”,也方便单元测试的编写。

如果将注解标注在私有属性上,则在单元测试时很难用编程的方法设置属性值

对类集合进行标注

如果对类中集合类的变量或方法入参进行@Autowired标注,那么Spring会将容器中类型匹配的所有Bean都自动注入进来



Spring如果发现变量是一个List和Map集合类,它会将容器中所有匹配集合元素类型的所有Bean都注入进来。

在②将实现Plugin接口的Bean注入Map集合,其中key是Bean的名字,value是所有实现了Plugin的Bean

在这里,Plugin是一个接口,它有两个实现类,分别是OnePlugin和TwoPlugin



通过@Component标注为Bean,Spring会将OnePlugin和TwoPlugin都注入plugins。

默认情况下,加载顺序是不确定,可以通过@Order注解或实现Ordered接口来决定Bean加载顺序,值越小,优先被加载

对延迟加载注入的支持

Spring支持延迟依赖注入,在Spring容器启动的时候,对于在Bean上标注@Lazy及@Autowired注解的属性,不会立即注入属性值,而是延迟到调用此属性的时候才注入属性值



对Bean实施延迟依赖注入,注意@Lazy注解必须同时标注在属性及目标Bean上,如①和②,否则无效

对标准注解的支持

Spring还支持JSR-250中定义的@Resource和JSR-330中定义的@Inject注解,功能都和@Autowired类似,都是对类变更及方法入参提供自动注入功能。

@Resource注解要求提供一个Bean名称的属性,如果属性为空,则自动采用标注处的变量名或方法名为Bean的名称

@Component
public class Boss{
private Car car;
@Resource("car")
private void setCar(Car car){
system.out.println("execute in setCar");
this.car=car;
}


@Autowired默认按类型匹配注入Bean,@Resource按名称匹配注入Bean

@Inject和@Autowired同样是按类型匹配注入Bean,只不过没有required属性。

可见@Autowired更强大

Bean作用范围及及生命过程方法

Bean作用范围

通过注解配置的Bean和通过<bean>配置的Bean一样,默认的作用范围都是singleton

Spring为注解配置提供了一个@Scope注解,可以通过它显式指定Bean的作用范围



生命过程方法

在使用<bean>进行配置时,可以通过init-method和destory-method属性指定Bean的初始化及容器销毁前执行的方法。Spring支持JSR-250中定义的@PostConstruct和@PreDestory注解,相当于init-method和destory-method属性的功能,不过在使用注解时,可以在一个Bean中定义多个@PostConstruct和@PreDestory





说明Spring先调用Boss的构造函数实例化Bean,再执行@Autowired进行自动注入,然后分别执行标注了@PostConstruct的方法(顺序随机),在容器关闭时,分别执行标注了@PreDestroy的方法

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-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd" >
<context:component-scan base-package="com.smart.anno"/>
</beans>


基于Java类的配置

使用Java类提供Bean定义信息

JavaConfig是Spring的一个子项目,通过Java类的方式提供Bean的定义信息

普通的POJO只要标注@Configuration注解,就可以为Spring容器提供Bean定义的信息。每个标注了@Bean的类方法都相当于提供了一个Bean的定义信息

package com.smart.conf;
//表示是一个配置信息提供类
@Configuration
public class AppConf {
//②以下两个方法定义了两个Bean,并提供了Bean的实例化逻辑
@Bean
public UserDao userDao(){
return new UserDao();
}
@Bean
public LogDao logDao(){
return new LogDao();
}
//③定义了logonService的Bean
@Bean
public LogonService logonService(){
LogonService logonService=new LogonService();
//将②和③处定义的Bean注入logonService Bean中
logonService.setLogDao(logDao());
logonService.setUserDao(userDao());
return  logonService;
}
}


标注@Configuration注解,说明这个类可用于为Spring提供Bean的定义信息,该类的方法可标注@Bean注解,Bean的类型由方法返回的类型决定,名称默认和方法名相同,也可以通过入参显式指定名称,如@Bean(name=”userDao”).@Bean所标注的方法提供了Bean 的实例化逻辑

与以下XML等效



基于Java类的配置方式和基于XML或基于注解的配置方式相比,前者通过代码编程的方式可以更加灵活地实现Bean的实例化及Bean之间的装配;后两者都是通过配置声明的方式,在灵活性上要稍逊一些,但配置更简单

如果Bean在多个@Configuration配置类中定义,例如UserDao和LogDao这两个Bean在DaoConfig中定义,而logonService Bean在ServiceConfig中定义,logonService Bean需要引用DaoConfig中定义的两个Bean



由于@Configuration注解类本身已经标注了@Component注解,所以任何标注了@Configuration的类,相当于标注了@Component,它们可以像普通的Bean注入其他的Bean。DaoConfig标注了@Configuration注解后就成了一个Bean,它可以被自动注入ServiceConfig中



调用daoConfig的logDao和userDao()方法,就相当于将DaoConfig配置类中定义的Bean注入进来。

Spring会对配置类所有标注@Bean的方法进行AOP增强,将对Bean生命周期管理的逻辑植入进来。所以在②调用daoConfi.logDao和daoConfi.userDao()方法时,不是简单执行DaoConfig类中定义的方法逻辑,而是从Spring容器中返回相应的Bean,即多次调用daoConfig.logDao()返回的都是Spring容器中相同的Bean。

在@Bean处,还可以标注@Scope注解以控制Bean的作用域。如果在@Bean处标注了@Scope(“prototype”),则每次调用daoConfig.logDao()都会返回一个新的logDao Bean

Spring容器会自动对@Configuration的类进行AOP增强,以植入Spring容器对Bean的逻辑管理,所以使用基于Java类的配置必须保证Spring.aop类和CGLIB类包加载到类路径中

使用基于Java类的配置信息启动Spring容器

通过@Configuration类启动Spring容器

Srping提供了一个AnnotationConfigApplicationContext类,能直接通过标注@Configuration的Java类启动Spring容器

public class JavaConfigTest {
public static void main(String[] args) {
//使用@Configuration类童工的Bean定义信息启动容器
ApplicationContext ctx=new AnnotationConfigApplicationContext(AppConf.class);
LogonService logonService=ctx.getBean(LogonService.class);
logonService.printHelllo();
}
}


还支持通过编码的方式加载多个@Configuration配置类,然后通过刷新容器应用这些配置类

public class JavaConfigTest {
public static void main(String[] args) {
//使用@Configuration类童工的Bean定义信息启动容器
AnnotationConfigApplicationContext ctx=new AnnotationConfigApplicationContext();
ctx.register(DaoConfig.class);
ctx.register(ServiceConfig.class);
//刷新容器以应用这些应用类
ctx.refresh();
LogonService logonService=ctx.getBean(LogonService.class);
logonService.printHelllo();
}
}


register方法是AnnotationConfigApplicationContext中的方法,不要用Application

可以一个个地配置类,也可以通过@Import将多个配置组装到一个配置类中,这样仅需注册这个组装好的配置类就可以启动容器

@Configuration
@Import(DaoConfig.class)
public class ServiceConfig {
@Bean
public LogonService logonService(){
LogonService logonService = new LogonService();
return logonService;
}
}


通过XML配置引用@Configuration的配置

标注了@Configuration和标注了@Component的类一样是一个Bean,可以被Spring的<context:component-scan>扫描到,因此,如果希望将配置类组装到XML配置文件中,通过XML配置文件启动Spring容器,仅需在XML通过<context:component-scan>扫描到相应的配置类即可



通过@Configuration配置类引用XML配置信息

在Bean3.xml中定义了两个Bean



在@Configuration配置类中可以通过@ImportResource引入XML配置文件,在LogonAppConfig配置类中即可直接通过@Autowired引用XML配置文件中定义的Bean

//通过@ImportResource引入XML配置文件
@Configuration
@ImportResource("classpath:com/smart/conf/beans.xml")
public class LogonAppConfig {
//自动注入XML文件中定义的Bean
@Bean
@Autowired
public LogonService logonService(UserDao userDao,LogDao logDao){
LogonService logonService=new LogonService();
logonService.setUserDao(userDao);
logonService.setLogDao(logDao);
return  logonService;
}
}


基于Groovy DSL的配置

使用Groovy DSL提供Bean定义信息

Groovy是一种j基于JVM的敏捷开发语言。能与Java代码很好地结合和扩展现有代码。

Spring支持Groovy DSL进行Bean配置,类似于XML配置,只不过配置信息是由Groovy脚本表达的,可以实现任何复杂的Bean配置

beans {
//①声明context命名空间
xmlns context: "http://www.springframework.org/schema/context"    //导入命名空间
//②与注解混合使用,定义注解Bean扫描包路径
context.'component-scan'('base-package': "com.smart.groovy") {
'exclude-filter'('type': "aspectj", 'expression': "com.smart.xml.*")
}
//④读取app-conf.properties
def stream;
def config = new Properties();
try{
stream = new ClassPathResource('conf/app-conf.properties').inputStream
config.load(stream);
}finally {
if(stream!=null)
stream.close()
}
//⑥根据条件注入Bean
if( config.get("dataProvider") == "db"){
userDao(DbUserDao)
}else{
userDao(XmlUserDao)
}
//⑤配置无参构造函数Bean
logDao(LogDao){
bean->
bean.scope = "prototype"       //配置当前Bean的作用域
bean.initMethod="init"          //配置当前Bean的初始化方法
bean.destroyMethod="destory"   //配置当前Bean的销毁方法
bean.lazyInit =true             //配置当前Bean的延迟加载
}
//⑦配置有参构造函数注入Bean,参数是userDao
logonService(LogonService,userDao){
logDao = ref("logDao")              //⑧配置属性注入,引用Groovy定义Bean
mailService = ref("mailService")     //⑨配置属性注入,引用注解定义Bean
}
}


在⑤处,logDao表示定义Bean的名称,括号内的LogDao表示要定义Bean的类名称

在⑥根据配置问价所配置的dataProvider选项值决定要注入的Bean,是Groovy DSL配置Bean 的一个重要灵活性体现

在⑦配置一个有参构造函数Bean,括号内第一个参数表示定义Bean的类名称,第二个表示LogonService构造函数userDao,采用属性注入logDao引用,其中ref()表示要引用容器中定义的Bean,在⑨也通过属性注入mailService,此处引用的Bean 是通过注解定义的

通过GenericGroovyApplicationContext启动Spring容器

public class LogonServiceTest {
@Test
public void getBean(){
//加载指定Groovy Bean配置文件来创建容器
ApplicationContext ctx=
new GenericGroovyApplicationContext("classpath:com/smart/groovy.spring-context.groovy");
//加载Groovy定义的Bean
LogonService logonService=ctx.getBean(LogonService.class);
assertNotNull(logonService);
//加载注解定义的Bean
MailService mailService=ctx.getBean(MailService.class);
assertNotNull(mailService);
//判读注入的是不是DbUserDao
UserDao userDao=ctx.getBean(UserDao.class);
assertTrue(userDao instanceof DbUserDao);
}
}


通过编码方式动态添加Bean

通过DefaultListableBeanFactory

DefaultListableBeanFactory实现了ConfigurableListableBeanFactory接口,提供了可扩展配置,循环枚举的功能,可以通过此类实现Bean动态注入。

为了实现在Spring容器启动阶段能动态注入自定义Bean,保证动态注入的Bean也能被AOP所增强,需要实现Bean工厂后置器处理接口BeanFactoPostProcessor

@Component
public class UserServiceFactoryBean implements BeanFactoryPostProcessor{
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory bf) throws BeansException {
//将ConfigurableListableBeanFactory转换为DefaultListableBeanFactory
DefaultListableBeanFactory beanFactory=(DefaultListableBeanFactory)bf;
//通过BeanDifinitionBuilder创建Bean定义
BeanDefinitionBuilder beanDefinitionBuilder=BeanDefinitionBuilder.genericBeanDefinition(UserService.class);
//设置属性userDao,此属性引用已经定义bean:userDao
beanDefinitionBuilder.addPropertyReference("userDao","userDao");
//注册Bean定义
beanFactory.registerBeanDefinition("userService",beanDefinitionBuilder.getRawBeanDefinition());
//直接注册一个Bean实例
beanFactory.registerSingleton("userService2",new UserService());
}
}


扩展自定义标签

在Spring中,自定标签需以下几个步骤:

①采用XSD描述自定义标签的元素属性

②编写Bean定义的解析器

③注册自定义标签解析器

④绑定命名空间解析器

第一步是定义标签元素的XML结构,采用XSD描述自定标签的元素属性

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.smart.com/schema/service"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
targetNamespace="http://www.smart.com/schema/service" ①
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:import namespace="http://www.springframework.org/schema/beans"/> ②
<xsd:element name="user-service">  ③
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="beans:identifiedType">
<xsd:attribute name="dao" type="xsd:string" use="required"/>    ④
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
</xsd:schema>


在①处指定了一个自定义命名标签的命名空间,在②导入Spring本身的beans命名空间,在③处定义了一个user-service标签,并且④在beans:identifiedType基础上定义了user-service标签的扩展属性”dao”,类似于继承属性

编写用户服务标签解析类,需实现BeanDefinitionParser,注意导入的Element是org.w3c.dom.Element

import org.w3c.dom.Element;

public class UserServiceDefinitionParser implements BeanDefinitionParser {

public BeanDefinition parse(Element element, ParserContext parserContext) {
//通过BeanDefinitionBuilder创建Bean定义
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(UserService.class);
//获取自定义标签的属性
String dao = element.getAttribute("dao");
beanDefinitionBuilder.addPropertyReference("userDao",dao);
AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
//注册Bean定义
parserContext.registerBeanComponent(new BeanComponentDefinition( beanDefinition,"userService"));
return null;
}
}


在②处通过element元素获取自定义标签的属性dao,并将已定义的userDao以引用的方式动态注入UserService,在③处通过parserContext注册UserService定义

现在可以将UserServiceDefinitionParser解析器注册到Spring命名空间解析器,其继承了NamespaceHandlerSupport

public class UserServiceNamespaceHandler extends NamespaceHandlerSupport {

public void init() {
registerBeanDefinitionParser("user-service", new UserServiceDefinitionParser());
}
}


最后需要告诉Spring如何解析自定义标签,在源码resources目录创建META-INF文件夹,并在其中创建spring.handlers和spring.schemas两个问价,告诉Spring自定义标签的文档接口及解析它的类



至此,自定义标签工作全部完成,可以在Spring配置文件中使用自定义的标签



要使用自定义的标签,首先如①,在beans头部引用自定义标签的命名空间,并设置空间命名前缀“us”,之后就可以使用“us”标签声明定义的组件了

不同配置方式比较







适用场景

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  spring ioc
相关文章推荐