在IoC容器中装配Bean(精通Spring+4.x++企业应用开发实战 四)
2017-09-05 13:42
1011 查看
本章的pom.xml
Bean配置信息是Bean的元数据信息,由4方面组成
Spring容器,Bean配置信息,Bean实现类以及应用程序的关系
采取基于Schema的配置格式,文件头的声明会复杂一些,看一个示例:
在上面定义了3个命名空间
①默认命名空间:没有空间名,用于Spring Bean的定义
②xsi标准命名空间:这个命名空间用于为每个文档中的命名空间指定响应的Schema样式文件,是W3C定义的标准命名空间
③aop命名空间:这个命名空间是Spring配置AOP的命名空间,即一种自定义的命名空间
命名空间的定义分为两个步骤:
第一步指定命名空间的名称
需要指定命名空间的缩略名和全名,如
aop为命名空间的别名,文档后面的元素可以通过命名空间的别名加以区分,如<aop:config/>
而http://www.springframework.org/schema/aop为空间的全限定名,习惯上用文档发布机构的官方网站或相关网站目录作为全限定名
如果命名空间的别名为空,则表示该命名空间为文档默认命名空间。文档中无命名空间前缀的元素都属于默认命名空间,如<beans/>属于在①处定义的默认命名空间
第二步 指定命名空间在Schema文档格式文件的位置,用空格或回车换行分隔
为每个命名空间指定对应的Schema文档格式的定义文件(图中的④),语法如下:
使用空格或回车分隔
在xsi:schemaLocation中,如
<?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.Elementimport 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命名空间解析器,其继承了NamespaceHandlerSupportpublic 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”标签声明定义的组件了不同配置方式比较
适用场景
相关文章推荐
- Ioc容器 (精通Spring+4.x++企业应用开发实战 三)
- 《精通Spring 4.X企业应用开发实战》读书笔记1-1(IoC容器和Bean)
- Spring容器高级主题(精通Spring+4.x++企业应用开发实战 五)
- 学习《spring 3.x企业应用开发实战》之在IoC容器中装配Bean
- 精通Spring 4.x企业应用开发实战——IoC(三)
- Spring 入门实例 简易登录系统(精通Spring+4.x++企业应用开发实战 学习笔记一)
- 论坛开发实例 (精通Spring+4.x++企业应用开发实战)
- Spring基础(精通Spring+4.x++企业应用开发实战 第7章)
- 精通Spring 4.x企业应用开发实战——IoC(五)
- Spring对DAO的支持(精通Spring+4.x++企业应用开发实战 第十章)
- 基于@AspectJ和Schema的AOP(精通Spring+4.x++企业应用开发实战 第八章)
- 《Spring 3.x 企业应用开发实战》学习笔记 第三章 IoC容器概述 3.5 Bean的生命周期
- Spring的事务管理难点剖析(精通Spring+4.x++企业应用开发实战 第12章)
- 《Spring 3.x 企业应用开发实战》学习笔记 第三章 IoC容器概述 3.5 Bean的生命周期
- 《精通Spring4.X企业应用开发实战》读后感第五章(装配Bean,依赖注入)
- Spring SpEL(精通Spring+4.x++企业应用开发实战 第九章)
- 精通Spring 4.x企业应用开发实战——IoC(四)
- Spring boot入门实例 简易登录(精通Spring+4.x++企业应用开发实战 学习笔记二)
- 《精通Spring4.X企业应用开发实战》读后感第五章(通过编码方式动态添加Bean)
- 精通Spring 4.x企业应用开发实战——高级主题(六)