Version 5.0.2.RELEASE,地址:https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-introduction
有人觉得学好Spring需要深入源码,但是我认为,除非你需要借鉴和利用Spring中的设计开发元素来完成自己的框架开发,不然没必要深入源码来学习Spring的使用(很多时候这些东西看了也很快遗忘)。自我感觉我对Spring的了解还不够,尝试简单翻译一下官方文档,仅供参考。
IOC
beans和context两个包是IOC的基础,BeanFactory接口提供了管理bean的机制,而 ApplicationContext是BeanFactory的一个子接口,它增加了AOP的整合,资源国际化,事件发布,以及应用层的context,比如WebApplicationContext。
简单点的说,BeanFactory提供了配置框架和基本功能,ApplicationContext增加了企业开发需要的特性,Spring的IOC容器一般也就是指ApplicationContext。
IOC概述
ApplicationContext有不同的实现,像ClassPathXmlApplicationContext,FileSystemXmlApplicationContext。可以使用xml,注解以及java代码来配置bean(xml是一种传统的方式)
IOC依赖某种形式的配置元数据,这个配置的元数据(一般是xml,注解,或者java代码)告诉IOC容器怎么初始化,配置以及装配对象。这所说的java代码配置方式是指使用@Bean,@Configureation来配置的bean
xml的配置方式:
1 | <?xml version="1.0" encoding="UTF-8"?> |
容器概述:
多个配置文件用逗号隔开
1 | ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml"); |
配置文件的组合:
下面的这些路径都是相对路径,不推荐开头使用斜杠
1 | <beans> |
另外路径要注意的是:
不要使用../来指定文件路径,可能会导致依赖当前应用之外的文件
特别是不要加上classpath来使用,比如:classpath:../services.xml
运行时会从最近配置的classpath中的父目录查找,classpath配置不同,导致的结果也不同
Bean概述
1 | // create and configure beans |
spring的bean的定义将会转化为BeanDefinition类
可以通过DefaultListableBeanFactory.registerSingleton()或者registerBeanDefinition来注册容器之外产生的bean
一、bean的命名
bean的标识符必须唯一,一般情况下只有一个标识符,但可以有多个名称
在xml配置中,id, name 都是指的标识符,bean可以定义多个名称,在name属性中指定(逗号,分号或者空格分隔多个别名)
如果一个bean没有定义ID,则将会以它的simple name作为名字(首字母小写,如果多个大写字母开头,则保持原样)
定义别名:
实际上name属性中定义的名字也算别名
1 | <alias name="fromName" alias="toName"/> |
二、初始化bean
配置内部类,要加上$符号,也就是编译后的内部类的名称
配置静态工厂方法产生的类:
不用指定生成的类的类型,只要工厂类有对应的静态方法
1 | <bean id="clientService" class="examples.ClientService" factory-method="createInstance"/> |
配置实例工厂方法产生的类:
指定生成类的方法名,注意实例工厂使用的是factory-bean而不是class
1 | <!-- the factory bean, which contains a method called createInstance() --> |
依赖注入
DI主要有两种形式:构造器注入和setter注入
一、构造器注入:
需要有一个有参的构造函数
1 | public class Foo { |
如果bar和baz没有继承关系,则在构造参数中不需指明索引或类型
1 | <beans> |
指定类型的参数匹配:
1 | <bean id="exampleBean" class="examples.ExampleBean"> |
指定参数索引:避免歧义
1 | <bean id="exampleBean" class="examples.ExampleBean"> |
指定参数名称赋值:同样是避免歧义
1 | <bean id="exampleBean" class="examples.ExampleBean"> |
由于在运行时参数名称是不可见的(编译时未打开debug flag,即javac –g:vars),这时候需要使用JDK注解来说明参数名称
1 | public class ExampleBean { |
二、setter注入:
1 | <bean id="id" class="com.loster.li.Id"> |
setter注入是在调用一个无参的构造函数,或者无参的静态工厂方法之后返回bean后的注入。基于setter和基于构造器可以混用,混用原则:
构造器注入适合强依赖,setter注入适合选择性依赖。同时在setter方法上加上@Required注解可以表示这个注入是必须的
构造器注入是不能为null的,不然会报错,所以spring推荐用构造器注入,换句话说setter注入可以是null的。同时通过构造器注入的bean是处于完全初始化的状态
对于属性赋值,spring支持字符串转int,long,String,boolean等
三、循环依赖问题
默认情况下,spring容器在启动的时候创建bean。lazy-init属性可以让bean在访问的时候再创建
如果类A使用构造器注入类B,类B又使用构造器注入类A,在运行的时候,Spring会抛出BeanCurrentlyInCreationException
可行的解决办法:让某些类使用setter注入,而不是构造器注入,或者只用setter注入。因此哪怕不推荐,你也可以使用setter注入来构造循环依赖
spring会尽量晚的进行属性注入和解决依赖问题,这表示有可能在容器正确加载之后也可能抛异常。比如类属性不存在或者不合法,这也是为什么spirng默认是预先初始化,把发现异常的时间提前。
如果bean A依赖bean B,spring容器在初始的时候会先初始化B
四、依赖注入的案例
setter注入:
1 | <bean id="exampleBean" class="examples.ExampleBean"> |
1 | public class ExampleBean { |
构造器注入:
1 | <bean id="exampleBean" class="examples.ExampleBean"> |
1 | public class ExampleBean { |
静态和实例工厂配置(和构造函数类似):
注意:
- 静态工厂返回的bean可以并不是该静态工厂方法所在的类,静态工厂配置的id是工厂生成的类的ID,class是静态工厂所在的class
- 非静态的实例工厂方法的使用方法类似,只不过把静态工厂中配置的class改成factory-bean,因为非静态的,需要工厂实例存在
1 | <bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance"> |
1 | public class ExampleBean { |
依赖的详细配置
一、直接的赋值:
使用value属性
1 | <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> |
使用p命名空间来简化赋值
1 | <beans xmlns="http://www.springframework.org/schema/beans" |
配置java.util.Properties实例:
1 | <bean id="mappings" |
idref使用
可以使用在constructor-arg和property标签上,注意它不是引用
1 | <bean id="theTargetBean" class="..."/> |
等同于:
1 | <bean id="theTargetBean" class="..." /> |
推荐用上面,因为在运行之前就能够发现问题。常用在配置代理拦截类,防止拼写错误
二、引用其他类
在constructor-arg和property标签上可以使用ref来引用其他类
最简单的引用方式:
可以用来引用当前容器或者父容器的bean(bean的赋值内容可以是某个的name属性)
1 | <ref bean="someBean"/> |
引用父容器中的bean
1 | <!-- in the parent context --> |
1 | <!-- in the child (descendant) context --> |
三、直接在property和constructor-arg内部配置bean
1 | <bean id="outer" class="..."> |
内部bean定义不需要id和name,内部bean是匿名的,而且随着外部bean的创建而创建,所以它会忽略scope配置
四、配置集合属性
1 | <bean id="moreComplexObject" class="example.ComplexObject"> |
Map的key和value,以及set的value,都可以使用在下面这些元素上:
1 | bean | ref | idref | list | set | map | props | value | null |
集合的继承使用:
子集合可以覆盖父集合,类似于继承,同样适用于list,map,set
xml的merge属性需要作用于子集合上
1 | <beans> |
最后的结果:
1 | administrator=administrator@example.com |
对于list说,因为需要维护顺序,所以父集合的顺序自带子集合之前
指定类型的集合
java5之后自带的
1 | public class Foo { |
赋的值都会转成Fload
1 | <beans> |
五、空字符串以及null
空字符串
1 | <bean class="ExampleBean"> |
null:
1 | <bean class="ExampleBean"> |
六、p命名空间的使用
两种方法的对比:
用来取代property标签
1 | <beans xmlns="http://www.springframework.org/schema/beans" |
1 | <beans xmlns="http://www.springframework.org/schema/beans" |
七、c命名空间的使用
用于构造参数
1 | <beans xmlns="http://www.springframework.org/schema/beans" |
如果不清楚构造函数的名称是什么,可以使用参数的位置:
1 | <bean id="foo" class="x.y.Foo" c:_0-ref="bar" c:_1-ref="baz"/> |
八、嵌套赋值
注意,当bean初始化后,这里的fred和bob不能为null,不然会报空指针异常
1 | <bean id="foo" class="foo.Bar"> |
使用depends-on
在xml配置中,常用ref来表示一个bean要依赖另一个bean;但有时候依赖并不是直接的,这时候就需要depends-on属性,让被依赖的bean在之前初始化,同时销毁的时候,要依赖的bean先销毁
1 | <bean id="beanOne" class="ExampleBean" depends-on="manager"/> |
多个依赖bean的配置:
可以用逗号,分号,空格隔开
1 | <bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao"> |
lazy-init:懒初始化
对于单利的bean,是在ApplicationContext初始化的时候就会创建。也可以将bean配置成懒初始化,也就是在bean在第一次访问的时候创建
1 | <beans default-lazy-init="true"> |
自动匹配
可以在bean元素中使用autowire来自动匹配bean,有四种匹配模式:
模式 | 解释 |
---|---|
No | 无自动匹配,需要用ref来定义 |
byName | 根据名称匹配。比如一个bean有一个mater属性(即有setMaster()方法),spring将会找名字为master的bean,然后注入 |
byType | 根据类型匹配,如果有多个类型匹配,则会出错(除数组、集合、Map之外) |
constructor | 和byType类似,但是是作用在构造方法的参数上 |
当使用byType或者constructor模式,可以自动装配数组和类型化的集合,容器会从所有的候选bean中选取匹配的类注入。同时如果是自动装配一个map,map的key是String类型的,那么map的值会包括所有类型匹配的实例,而且map的key将会是对应的bean的名字。
缺陷:
不推荐一个项目只有很少的类使用autowire,应该保持风格的一致性
不能autowire原始类型,String,Classes
autowire 没有显示配置准确
…
排除不自动匹配的bean:
在bean标签中加入autowire-candidate=false可以避免该bean被拿来匹配,如果设置为false,@Autowired也不会匹配上
限制配置模式:
default-autowire-candidates=”*Repository”,表示只会让Repository结尾的类匹配,多个模式用都好隔开。注意,autowire-candidate的优先级要高于这个
方法注入(Method injection)
当一个单例的bean A中的方法在调用的使用需要依赖非单例的bean B,如果只像前面介绍的那样配置一下会出现问题,因为容器只会初始化A一次,因此只会设置一次bean B。当A的方法被调用时,不能给A提供新的B的实例。
这时候就需要我们手动的getBean来设置:
1 | // a class that uses a stateful Command-style class to perform some processing |
一、查找方法注入(Lookup method)
上面的方式由于和Spring耦合,并不推荐。
Lookup 方法注入能够重写容器管理的bean,然后返回对另一个bean的查找结果,通常用在有非单例bean的场景,类似于上面。Spring采用CGLIB重写Lookup method
一般是定义了一个抽象类,用来继承重写,也可以是实体类
1 | package fiona.apple; |
查找方法的写法:
1 | <public|protected> [abstract] <return-type> theMethodName(no-arguments); |
spring的配置:
1 | <!-- a stateful bean deployed as a prototype (non-singleton) --> |
当commandManager需要myCommand类的一个新实例时,可以调用createCommand()方法。需要注意的是myCommand要配置成prototype,不然没意义,每次返回的都是同一个
也可以是用注解的方式配置:
1 | public abstract class CommandManager { |
二、任意方法的替换
也是方法注入的一种,只不过用的很少
1 | public class MyValueCalculator { |
1 | /** |
Spring配置:
1 | <bean id="myValueCalculator" class="x.y.z.MyValueCalculator"> |
<arg-type/>
可以使用多个 ,String也可以简写成:
1 | java.lang.String |
bean的范围
这里范围指的是一个bean definition的作用范围,或者说由一个bean definition创建的一个对象的作用范围。一个bean definition可以创建多个实例
bean的范围目前可以使用的有六种,其中四种用于web环境
范围 | 描述 |
---|---|
单例(singleton) | 默认的,容器只创建一个实例 |
原型(prototype) | 多例的 |
request | 一个http request生命周期一个实例 |
session | 一个session 生命周期一个实例 |
application | 一个 ServletContext生命周期一个实例 |
websocket | 一个WebSocket生命周期一个实例 |
单例
默认的范围。该bean的bean definition只会生成一个实例,作用于整个IOC容器的使用。
注意,不是一个ClassLoader一个实例,而是一个IOC容器一个实例
1 | <bean id="accountService" class="com.foo.DefaultAccountService"/> |
原型(prototype)范围
- 原型类似于原型模式,通过一个原型能够复制出多个实例
- 一个prototype适用于状态变化的bean,而singleton适用于无状态的bean。如果一个bean是prototype,每一次使用(注入)都是一个新的bean
- 需要注意的时候,容器不会完全管理多例bean的生命周期,调用方需要合理的销毁bean,比如释放占用的资源
依赖的注意
当使用prototype bean时,相关的依赖解析是在运行时才进行的
Request,session,application,Websocket范围
在web环境下才会使用这些范围,如果你使用在ClassPathXmlApplicationContext中,将会报异常
一、初始化web配置
使用这些范围要初始化相关的web环境,这取决于使用的是什么web容器。在Spring MVC中,请求通过DispatcherServlet进行处理,不需要进行特别的设置。
如果使用的是Servlet2.5的web容器,并且请求其他的方法进行处理(比如Struts),这是就需要注册org.springframework.web.context.request.RequestContextListener.ServletRequestListener
,对于Servlet3.0+,可以通过编程继承实现WebApplicationInitializer接口,或者使用下面的xml配置:
1 | <web-app> |
如果配置监听器有问题,可以使用Spring的RequestContextFilter:
1 | <web-app> |
DispatcherServlet,RequestContextListener,以及RequetContextFilter都是做的同样一件事,就是将HTTP request绑定到请求线程。
二、Request 范围
通过下面的方式配置:
1 | <bean id="loginAction" class="com.foo.LoginAction" scope="request"/> |
代码配置:
1 |
|
三、Session范围
1 | <bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/> |
1 |
|
四、Application范围
也就是ServletContext级别,作为ServletContext一个属性保存
1 |
|
1 |
|
五、依赖问题
如果将HTTP request注入到一个生命周期更长的一个作用域(此时request可能已经不存在),可以采用注入一个AOP代理类的方式来实现,spring提供了如下的配置方式:
1 | <?xml version="1.0" encoding="UTF-8"?> |
原因:
如果不采用上面的代理形式,由于单例的bean只会初始化一次,它的依赖也只会初始化一次,因此userService操作的userPreferences都是一开始注入的那个,这显然不是我们想要的结果
采用上面的代理形式后,每当调用代理类的方法时,代理类会从当前的HTTP Session中获取UserPreferences对象,然后代理这个对象
使用aop:scoped-proxy标签时,将会使用CGLIB创建代理对象,CGLIB只会代理public方法,对于非public方法,代理无效。
也可以使用JDK的基于接口的代理,只需要将proxy-target-class设置为false
1 | <!-- DefaultUserPreferences implements the UserPreferences interface --> |
注意:aop:scoped-proxy对于FactoryBean的实现类不起作用,返回的仍是factory bean本身,而不是从getObject拿到的返回值
自定义范围
自定义自己的范围,需要实现org.springframework.beans.factory.config.Scope
接口:
主要有四个方法:
从scope中获取对象的方法
从scope中移除对象
注册销毁的时候的回调函数
获取会话标识符
1
2
3
4
5 > Object get(String name, ObjectFactory objectFactory)
> Object remove(String name)
> void registerDestructionCallback(String name, Runnable destructionCallback)
> String getConversationId()
>
向Spring容器注册一个Scope(该方法来源于ConfigurableBeanFactory接口):
第一个参数中的scope名称是唯一的,第二个参数是Scope的实现类
1 | void registerScope(String scopeName, Scope scope); |
也可以声明式的注册Scope:
1 | <?xml version="1.0" encoding="UTF-8"?> |
自定义bean的特性
生命周期回调
如果想介入容器管理的bean的生命周期,可以实现InitializingBean以及DisposableBean接口,对于前者,容器会调用afterPropertiesSet方法,对于后者会调用destroy方法。
注意:
Spring推荐使用@PostConstruct和@PreDistory注解来实现生命周期的回掉,或者使用init方法或者destroy方法来实现
在Spring的内部是使用BeanPostProcessor的实现类来处理任何的实现的生命周期的回调(对内的接口,功能更多),因此,可以实现该接口来自定义生命周期的回调处理
除了初始化和销毁的回调,Spring管理的bean同时也实现了LifeCycle的接口,从而能够参与容器本身的创建和销毁的声明周期(跟容器挂钩而不是跟bean的生命周期挂钩)
一、初始化回调
InitializingBean接口允许bean来实现初始化工作(相关的属性都设置之后),该接口含有一个方法:
1 | void afterPropertiesSet() throws Exception; |
不推荐使用该接口,因为会和spring耦合。可以使用@PostConstruct注解或自己实现一个POJO的初始化方法。如果是使用xml来配置spring的,使用init-method属性来配置初始化方法名(void型,无参的方法)
1 | <bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/> |
1 | public class ExampleBean { |
上面的代码等同于:
1 | <bean id="exampleInitBean" class="examples.AnotherExampleBean"/> |
1 | <bean id="exampleInitBean" class="examples.AnotherExampleBean"/> |
二、销毁回调
实现DisposableBean接口就能实现销毁回调方法,该接口只有一个方法:
1 | void destroy() throws Exception; |
同上实现该方法同在xml配置中配置destroy-method的效果一样
destroy-method的值也可以指向一个类,该类实现了
java.lang.AutoCloseable
或者java.io.Closeable
接口,这样在销毁的时候会调用接口中的close或者shutdown方法
三、默认初始化和销毁方法(全局的)
如果不使用spring提供的接口来实现初始化和销毁回调,仅仅在类中写了名如init(),initialize(),dispose()等方法,并且在一个项目中都按这种方式命名,这时我们可以配置让容器去“查找”这些初始化和销毁方法并作用在每个bean上,下面是例子:
1 | public class DefaultBlogService implements BlogService { |
1 | <beans default-init-method="init"> |
同样,销毁回调使用default-destroy-method属性来配置
spring是在bean满足初始化依赖后才会进行相关的初始化工作,因此初始化回调作用在原生的bean上,对于AOP来说,此时是不生效的。只有当bean完全初始化后才会创建代理对象。
四、生命周期组合机制
spring提供了三种控制bean生命周期的方式:
InitializingBea 和DisposableBean接口
自定义init()和destroy()方法
使用@PostConstruct或者@PreDestroy注解
如果多个生命周期的方法名称重复,该方法只会执行一次,如果有多个,会按照一定的顺序执行,执行顺序如下:
@PostConstruct
InitializingBean接口
自定义的init()方法
销毁方法顺序同上
五、启动和停止回调
Lifecycle提供了一些方法来满足对象声明周期的要求(比如启动或者停止一些后台程序)
1 | public interface Lifecycle { |
当IOC接收到启动或者停止信号时,就会依次调用这些方法。spring通过委托LifecycleProcessor来实现。
由于bean之间存在依赖,因此启动和停止的顺序应该和依赖关系一致,但是有时这些依赖并不能直接知道,这时候spring提供了SmartLifecycle接口来解决,解决思路:
1 | public interface SmartLifecycle extends Lifecycle, Phased { |
通过实现Phase接口的getPhase方法,返回一个int值,值越小表示启动优先级越高,销毁优先级越低,只实现普通LifeCycle的类的phase是0,因此如果你定义的类的phase是负数,那么表示会在这些普通类之前初始化
注意上面的SmartLifecycle提供了一个run方法,这是在关闭过程结束后新建线程异步运行的。这个异步动作默认现实30s,可以自己配置。
六、非web环境关闭IOC容器
ApplicationContext是基于web环境的,在非web,比如桌面应用开发环境,可以注册一个JVM hook来实现关闭IOC
1 | import org.springframework.context.ConfigurableApplicationContext; |
ApplicationContextAware和BeanNameAware
ApplicationContextAware类持有一个ApplicationContext引用,在ApplicationContext启动后,可以用该引用获取相应的bean,但是不推荐这种方法,因为违背了IOC的规则
spring2.5之后,也可以采用autowire这只ApplicationContext引用(构造函数注入和set注入,或者使用@AutoWire的方式注入到构造函数,类的字段,或者其他的方法中)
BeanNameAware则接口提供了获取实现类在IOC中的id的能力,注意该接口的回调是在初始化方法之前完成
其他的Aware接口详见官方文档(使用这些Aware会和spring代码耦合)
Bean definition的继承
一个bean definition(就是xml中bean的定义)包含很多的配置信息,包括构造参数,属性值,以及容器管理的初始化方法,静态工厂方法名等。一个子的bean definition会继承父的配置信息,子的可以覆盖父的某些配置信息,或者增加其他的配置信息。例子如下:
1 | <bean id="inheritedTestBean" abstract="true" |
但是下面定义会总是从子定义中获取:depends on, autowire mode, dependency check, singleton, lazy init
如果父定义没有指定实现类,则abstract属性必须有
容器扩展点
正常情况下不会用到这些知识,但是如果需要使用到spring的内部机制就需要这些类了
BeanPostProcessor
是一个回调接口,用来写bean的初始化逻辑,依赖解决逻辑等。可以实例化多个该类,并能设置优先级。该类的scope是整个容器。spring AOP中创建代理类用到了该接口。
案例:
1 | package scripting; |
配置方法很简单,都不需要写id
1 | <bean class="scripting.InstantiationTracingBeanPostProcessor"/> |
BeanFactoryPostProcessor
可以在初始化其他bean之前能够读取配置元数据(在bean初始化之前),并且能够更改这些数据,因此通过该接口自可以自定义配置元数据 。同样也可以实例化多个实现类并设置优先级。同样scope也是容器级别的。spring提供了一些该接口的实现类,比如:PropertyPlaceholderConfigurer(placeholder中文为占位符的意思)和PropertyOverrideConfigurer。下面是案例:动态修改连接池的相关信息
1 | <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> |
${}占位符的内容来自于配置的jdbc.properties文件
另一种类似的配置方式,使用context命名空间:
1 | <context:property-placeholder location="classpath:com/foo/jdbc.properties"/> |
该类另一种使用方法是在运行时动态替换class的名字
1 | <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> |
另一个实现类PropertyOverrideConfigurer的用法:用来覆盖属性值,同样也是通过properties文件来配置,而且可以配置多层的属性
配置方式:
1 | beanName.property=value |
如:
1 | dataSource.driverClassName=com.mysql.jdbc.Driver |
同样,使用context来更方便的配置:
1 | <context:property-override location="classpath:override.properties"/> |
使用FactoryBean自定义初始化逻辑
自定义的FactoryBean的实现类是可以被IOC使用的(可插拔),该接口控制了bean的初始化逻辑。获取FactoryBean本体类的方法:getBean(“&myBean”),注意到前面多了个&符号,表示是获取FactoryBean的实现类,而不是从IOC容器获取某个类。
容器的注解方式的配置
注解方式比XML方式的配置更好吗 答案是it depends
注解的优点:配置方便 缺点:和代码耦合, 需要重新编译
xml的优缺点和上面相反
注意:
注解注入是在xml注入之前进行的,因此后者的相关配置会覆盖前者的配置
使用注解一般要配置BeanPostProcessor:
1 | <?xml version="1.0" encoding="UTF-8"?> |
上面的配置注册了一系列post-processors 包括:
AutowiredAnnotationBeanPostProcessor, CommonAnnotationBeanPostProcessor, PersistenceAnnotationBeanPostProcessor,RequiredAnnotationBeanPostProcessor
@Required
表示属性必须注入,避免运行时报空指针
1 | public class SimpleMovieLister { |
@Autowired
下面的案例可以使用@Inject注解来代替@Autowired
作用在构造方法上:
1 | public class MovieRecommender { |
注意:
如果bean只有一个构造方法依赖目标类,则可以省略@Autowired,如果有多个构造函数依赖某个类,至少有一个构造函数有注解(待确认,可以自行验证)
作用在setter方法上:
1 | public class SimpleMovieLister { |
作用在任意的方法上:
1 | public class MovieRecommender { |
也可以作用在数组和集合上:会从容器中查找所有的合适的类型注入
1 | public class MovieRecommender { |
1 | public class MovieRecommender { |
如果希望容器中的元素是有顺序的,可以相关类上添加@Order或者@Priority注解
也可以注入到Map中,只要map的key是String就行,注入的时候会存bean的名字
1 | public class MovieRecommender { |
当没有匹配的类可以注入时,默认注入时失败的,当然可以通过下面的方式修改这一特性:
1 | public class SimpleMovieLister { |
一个类里面只有一个构造方法可以被标记为required
使用required属性比@Required注解更好,@Required要求更强
也可以对Spring内部提供的一些类进行注解依赖:
1 | public class MovieRecommender { |
@Autowired @Inject @Resource @Value是通过BeanPostProcessor来处理的,如果自定义了自己的实现类,这些注解就不会起作用,只能通过xml来注入
通过@Primary设置注入的优先级
通过byType的注入方式可能要面临多个候选类的情况,这时候可以@Primary来调节注入的优先级
1 |
|
firstMovieCatalog将会优先被注入,用xml写:
1 | <?xml version="1.0" encoding="UTF-8"?> |
使用@Qualifier来注入
1 | public class MovieRecommender { |
可以作用在构造方法的参数上
1 | public class MovieRecommender { |
bean的名字是默认的qualifier值,因此可以使用该注解实现byName的注入
注意使用qualifier只会收窄匹配范围,前提还是按类型匹配
qualifier可以不唯一,因此也可以注入到集合中去
如果目的是想按byName注入,Spring更推荐使用@Resource注解,@Resource可以实现类型不相干注入
@Autowired能作用在构造方法,方法参数以及属性,而@Resource只能作用在属性和setter方法上
一个类也可以自己注入自己,但是这个注入处理优先级最低
相当于:
1 | <?xml version="1.0" encoding="UTF-8"?> |
也可以自定义自己的qualifier注解:
不仅如此,自定义注解还可以添加其他的描述,当所有描述都匹配后才会注入
1 | ({ElementType.FIELD, ElementType.PARAMETER}) |
使用:
1 | public class MovieRecommender { |
1 | <?xml version="1.0" encoding="UTF-8"?> |
泛型注入
假设有两个实现了Store<泛型>接口的类
1 |
|
然后可以自动装配:
1 |
|
泛型注入可以作用在Lsit, Map和Array上
1 | // Inject all Store beans as long as they have an <Integer> generic |
CustomAutowireConfigurer自定义注解
参考官方文档
@Resource
可以用来注入属性和setter方法,name指的是bean的name(id)
1 | public class SimpleMovieLister { |
如果不指定名字,会默认使用属性的名字和setter方法中参数的名字,如果找不到名字,@Resource会按照类型注入
@PostConstruct和@PreDestroy
1 | public class CachingMovieLister { |
Classpath扫描以及注入
@Componen等其他注解注入
Spring提供@Component, @Service, @Controller,@Repository注解注入
@Component适用于任何注入的类,其他几个作用一样,只不过带有特殊的含义。
Spring提供的这些注解也可以用在元注解上,比如@Service注解的定义如下:
1 | (ElementType.TYPE) |
不仅可以添加一个,也可以组合添加使用,比如Spring MVC的@Controller就是@Controller和@ResponseBody组成的
使用注解扫描自动注入相关类
使用@ComponentScan(可以使用逗号,分号和空格分隔,basePackages可以省略,这样会使用默认的value属性)
1 |
|
等同于:
1 | <?xml version="1.0" encoding="UTF-8"?> |
注意,使用component-scan默认开启了annotation-config,所以没有必要手动再添加:\
同时,开启注解扫描也默认包含了AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor,因此可以识别这些注解
@ComponentScan中使用过滤器
有以下的过滤类型:
Filter Type | Example Expression | Description |
---|---|---|
annotation (default) | org.example.SomeAnnotation |
An annotation to be present at the type level in target components. |
assignable | org.example.SomeClass |
A class (or interface) that the target components are assignable to (extend/implement). |
aspectj | org.example..*Service+ |
An AspectJ type expression to be matched by the target components. |
regex | org\.example\.Default.* |
A regex expression to be matched by the target components class names. |
custom | org.example.MyTypeFilter |
A custom implementation of the org.springframework.core.type .TypeFilter interface |
使用:
1 |
|
等同于:
1 | <beans> |
可以在注解中使用useDefaultFilters=false和在xml中context:component-scan的属性中设置use-default-filters=”false”来屏蔽前面讲过的四个注解的注入作用(实际是取消默认过滤器的作用)
Component中提供bean的配置元数据
@Component类中可以使用@Bean来提供bean definition元数据,并供其他bean definition autowire(待确定)
@Qualifier起作用的前提是有@Bean,publicInstance方法提供了一个bean definition,bean的名字就是publicInstance(一般用在@Configuration注解的配置类中)
1 |
|
1 |
|
InjectionPoint表示注入点(让该创建bean的方法运行的注入点)
1 |
|
@Bean在@Component和在@Configuration的区别:
在@Configuration中,@Bean作用的工厂方法返回的对象是经过CGLIB代理的,而@Component中的@Bean注解的工厂方法返回的是原生Java对象
参考博客:https://blog.csdn.net/ttjxtjx/article/details/49866011
注意:@Bean的方法可以使静态的,适用于定义PostProcessor,因为它们初始化的优先级高,但是这时返回的类不会被Spring代理,因为是静态的。
@Configuration中的@Bean的方法的位置可以随意放,但是方法不能是private和final的
注入类的名称
前面介绍的四个注解都有一个name属性,给注入的类提供名称,不注明name属性,则用首字母小写的方式
1 | "myMovieLister") ( |
1 |
|
自定义命名规则:实现BeanNameGenerator接口
1 |
|
1 | <beans> |
指定Scope
默认是singleton的,也可以显示定义:
1 | "prototype") ( |
同时也可以自定义scope的resolve类:
1 |
|
1 | <beans> |
对于web中的不同的scope可能会产生依赖,这时候要传递代理类,也可以指定代理的方式(不代理,接口方式,目标类方式)
1 |
|
1 | <beans> |
使用注解提供qualifier元数据
前面也讨论过这些qualifier,只不过这里使用注解方式
1 |
|
加快classpath扫描:创建索引
1 | <dependencies> |
使用JSR 330标准注解
这里简略带过,主要有以下几个注解:
@Inject @Name @Singleton @Provider
基于java代码的容器配置
使用@Bean和@Configuration
主要使用到@Bean(提供bean definition 和xml中的bean标签一样)和@Configuration(提供配置元数据)两个注解
1 |
|
前面我们看到可以在@Component中使用@Bean注解, 这种@Bean不和@Configuration一起使用的方式,是一种简化模式
使用AnnotationConfigApplicationContext初始化容器
AnnotationConfigApplicationContext能够处理@Configuration、@Component以及JSR-330注解的类
使用:
1 | public static void main(String[] args) { |
任何@Component和JSR-330的注解类都可以作为输入
1 | public static void main(String[] args) { |
使用register显示注册配置类:
1 | public static void main(String[] args) { |
开启包扫描:
1 |
|
1 | public static void main(String[] args) { |
web环境下的使用:
web环境要使用AnnotationConfigWebApplicationContext来配置ContextLoaderListener监听器,Spring的DispatcherServlet等,例如:
1 | <web-app> |
使用@Bean注解
1 |
|
使用构造方法注入:
1 |
|
指定初始化和销毁的方法
1 | public class Foo { |
采用java代码配置方式,默认会识别close和shutdown来执行初始化或销毁回调,如果类中有该方法,但是不想让他们作为回调方法,可以使用下面的方式屏蔽,(DataSource一般都要屏蔽):
1
2
3
4
5 > "") (destroyMethod=
> public DataSource dataSource() throws NamingException {
> return (DataSource) jndiTemplate.lookup("MyDS");
> }
>
指定Scope
1 |
|
Scope的代理:
1 | // an HTTP Session-scoped bean exposed as a proxy |
指定Bean的名字:
1 |
|
添加Bean的描述:
1 |
|
使用@Configuration注解
该注解表示该类的作用是提供bean的定义的
注入bean的依赖(直接调用方法):
1 | @Configuration |
look-up注入:解决单例bean依赖原型bean的问题:
1 | public abstract class CommandManager { |
1 |
|
@Bean的调用次数问题:
1 |
|
组合Config类:
使用import
一个配置类可以导入其他的配置类,Spring4.2之后也可以导入component类
1 |
|
这时候只需要显示的配置ConfigB的类就行了,因为B会依赖A:
1 | public static void main(String[] args) { |
实际使用中经常会遇到的案例:多个配置文件互相依赖
1 |
|
对BeanPostProcessor 或者BeanFactoryPostProcessor使用@Bean注解时,一般采用静态方法,这样不会过早初始化配置类,不然的话,使用@Autowired或者@Value作用在配置类上会失效
或者使用Autowire:
1 |
|
有时候并不知道使用Autowire的依赖类是在哪个配置类,这时候可以显示依赖配置类:
缺点:使配置文件耦合起来
1 |
|
其他注解:
@Lazy表示懒加载
@DependsOn添加显示依赖
有选择性的添加@Configuration类和@Bean方法:
Profile注解(参见官方文档)
组合xml和java配置
在xml配置文件中加入config配置类:
1 |
|
1 | <beans> |
1 | jdbc.url=jdbc:hsqldb:hsql://localhost/xdb |
因为Configuration本身就使用了@Component,所以上面的xml配置文件又可以改为:
注意:component-scan已经开启了annotation-config,不用再写
1 | <beans> |
在以java代码配置为主的配置中加入xml配置:使用@ImportResource
1 |
|
1 | properties-config.xml |
Environment抽象
Environment的主要涉及到两个方面:profiles和properties
profile用来表示bean definitions在逻辑上的区分,properties表示配置文件
Bean definition的profile
@Profile
案列:datasource的分环境配置
1 |
|
1 |
|
Profile注解也可以作用在注解上:
1 | (ElementType.TYPE) |
这样案例中的Profile注解可以用@Production代替
@Profile({“p1”,”p2”}):在profile p1和p2激活的时候才会生效
@Profile({“p1”,”!p2”}):p1激活,p2没激活的时候才会生效
@Profile也可以作用在方法上:
1 |
|
xml中的写法:
1 | <beans profile="development" |
1 | <beans profile="production" |
或者:
1 | <beans xmlns="http://www.springframework.org/schema/beans" |
激活Profile
常见的方式:
1 | AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); |
可以激活多个:
1 | ctx.getEnvironment().setActiveProfiles("profile1", "profile2"); |
在测试中可以使用:@ActiveProfiles
通过配置的方式:配置spring.profiles.active属性:
1 | -Dspring.profiles.active="profile1,profile2" |
默认Profile
在没有指定profile的情况下会默认开启的(默认profile的名字可以改)
1 |
|
PropertySource的抽象
Environment的另一个部分
1 | ApplicationContext ctx = new GenericApplicationContext(); |
Environment会从一系列PropertySource对象中寻找。Spring的StandardEnvironment配置了两个该类:一个表示JVM系统参数(System.getProperties()),另一个表示系统的环境变量(System.getenv())。系统属性会在环境变量之前被查找。除了StandardEnvironment还有StandardServletEnvironment,它里面包含了servlet的一些配置信息
添加自己的PropertySource
1 | ConfigurableApplicationContext ctx = new GenericApplicationContext(); |
@PropertySource
可以通过@PropertySource方便的添加该类
1 |
|
@PropertySource中可以使用已有的property:
1 |
|
这里面假设my.placeholder已经在注册的property resource里面了,比如系统属性或者环境变量里面。如果没有将会使用default/path。
同时下面这个占位符不关心customer属性在哪定义了,只要在Environment中就能够被读到:
1 | <beans> |
ApplicationContext的其他能力
ApplicationContext继承了BeanFactory接口,并提供了一下的能力;
通过MessageSource访问国际化信息
通过ResourceLoader接口访问资源
通过使用ApplicationEventPublisher向ApplicationListener的实现类发布事件
加载多个contexts
使用MessageSource国际化
当ApplicationContext加载后,会自动查找MessageSource的类。Spring提供了两个Message实现类:ResourceBundleMessageSource和StaticMessageSource。下面是案例:
list的每个value代表一个resource bundle
1 | <beans> |
1 | # in format.properties |
使用:
1 | public static void main(String[] args) { |
另一个使用了占位符的案例:
1 | <beans> |
1 | public class Example { |
1 | # in exceptions.properties |
MessageSource的实现类都带有JDK ResourceBundle的功能:
下面是按照约定定义的针对British的properties国际化文件:
1 | # in exceptions_en_GB.properties |
使用:
1 | public static void main(final String[] args) { |
web环境下ApplicationContext初始化
ContextLoaderListenerh会注册一个ApplicationContext,如果不指定contextConfigLocation的值,默认使用“/WEB-INF/applicationContext.xml”
1 | <context-param> |