基本概念
IoC(Inversion of Control)和DI(Dependency Injection)指的是一回事儿。
业务告诉容器需要哪些依赖。容器在创建bean时注入这些依赖项。怎么告诉容器呢?可以有三个地方:
-
- 构造函数参数
- 工厂方法参数
- 对象成员变量
最传统的方式就是自己创建依赖像实例,new一个。Spring官方文档还提到了一种模式是Service Locator pattern,我理解实现之一就是SPI。关于DI和Service Locator的比较,可以看下参考资料第一条的链接。
我理解bean就是受spring管理的对象。pojo就是不受spring管理的普通java对象。
Beans以及bean之间的依赖关系,在容器这边,就是配置元数据(the configuration metadata)。
在spring的语境下,容器就是ApplicationContext。实际上DI是一种设计模式,所以不用spring但是有实现DI/IoC的话,容器就是ApplicationContext的conterpart。这个和集合的容器不是一个概念哈。
核心包和类
- 核心包
-
- org.springframework.beans
- org.springframework.context
- 核心类
-
- BeanFactory - 提供框架配置和基本能力
- ApplicationContext - BeanFactory的子类,增加了4个功能
-
-
- 与Spring AOP的集成
- 国际化的支持(Message资源处理)
- 事件发送
- 应用层的上下文支持,如WebApplicationContext
-
容器
ApplicationContext负责实例化、配置和装配bean。那容器也不能自己拍脑袋决定bean要怎么实例化、配置和装配吖,要让容器工作,就得告诉它要怎么干活,所以得有个地方配置,就是配置元数据(configuration metadata)。现在有三种:xml,注解,Java代码写。
ApplicationContext现在有俩实现:
-
- ClassPathXmlApplicationContext
- FileSystemXmlApplicationContext
- 自己搞一个
XML中相关元素
-
- <import/>
- <beans/>
- <bean/>
// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml","daos.xml");
// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);
// use configured instance
List<String> userList = service.getUsernameList();
// 也可以分开,可以混着用,比如扫一些然后再读一些xml的
GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();
Bean
在容器里,bean都是以BeanDefinition对象的形式被抽象的。BeanDefinition包括这些信息:
-
- 全限定名-通常就是实际的实现类
- 行为配置元素,告诉容器bean要做哪些动作,比如范围(scope)、生命周期回调方法等。
- bean正常工作所需的其他bean引用,也叫作依赖或者合作者(collaborators)
- 其他实例化所需的设置项,如池子的大小限制、连接池的连接数量等
除了根据BeanDefinition创建bean,ApplicationContext也支持把已经创建好的对象作为bean注册进来。首先调用getBeanFactory()获取到DefaultListableBeanFactory(BeanFactory实现类),DefaultListableBeanFactory提供了方法
-
- registerSingleton(..)
- registerBeanDefinition(..)
注意的话,如果要这么干,越早注册越好。以免容器初始化auto wiring的时候出问题。
bean的命名
一个bean可以有一到多个坐标名称,但是多个bean不能重名。多个的实现,在spring里,一个作为name,其他作为alias。
XML配置里,有两个标签 id和name,id是只有一个的,name可以指定一个或多个,用逗号,分号,或者空格做分隔符都可以1。如果id和name都不提供,那容器就自己生成一个唯一名称(类名首字母小写,除非前两个字母都算大写,那保留大写)2。如果是大型系统里,要用其他模块提供的bean,但是需要重新取个名字,可以用alias(@Bean也提供):
<alias name="fromName" alias="toName"/>
初始Bean
在xml的bean中提供class标签项,对应的是BeanDefinition的Class字段 。
-
- 类提供构造函数,调用构造函数创建bean -- 调用的是无参的构造函数,如果要有状态,等构造完了之后再注入依赖
- 类提供静态工厂方法,调用工厂方法创建bean,创建的bean的类型由工厂方法决定 -- 还需要提供factory-method表示工厂方法名。Spring提供这个主要还是考虑到要兼容历史遗留库类。
- 现有bean的非静态工厂方法 -- class不填,factory-bean填现有bean的名字,factory-method填工厂方法名
静态工厂方法示例:
<bean id="clientService"
class="examples.ClientService"
factory-method="createInstance"/>
public class ClientService {
private static ClientService clientService = new ClientService();
private ClientService() {}
public static ClientService createInstance() {
return clientService;
}
}
现有bean的非静态工厂方法
<!-- the factory bean, which contains a method called createInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- inject any dependencies required by this locator bean -->
</bean>
<!-- the bean to be created via the factory bean -->
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
}
- 嵌套类
-
- com.example.外层类名$嵌套类
-
- com.example.外层类名.嵌套类
确定bean运行时的类型
bean定义类型和运行时类型不一定一致的。
BeanFactory.getType
依赖
依赖注入
依赖注入有两大变种:基于构造函数的和基于setter的。
基于构造函数的依赖注入
- 构造函数参数解析
根据参数类型进行,一般和xml定义的顺序一致。如果光靠类型不够,就在constructor-arg加上index(下标从0开始)。或者也可以加上name3。
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>
基于setter的依赖注入
可以使用BeanDefinition和PropertyEditor手写代码注入。但是不推荐
通常做法还是使用 XML或者注解(@Component, @Controller,)或者@Configuration 类里的@Bean 方法提供给容器。
Spring是推荐多用基于构造函数的依赖注入的,对于基于setter的依赖注入,官方推荐在这个依赖是optional的时候使用。
依赖解析的顺序
- 实例化ApplicationContext,在这个过程中会加载元配置
- 挨个实例化bean,如果bean有依赖项,会在创建的时候提供
- 每个属性或者构造函数参数,都是一个需要设置的定义或者引用
- 属性或者参数,需要的话,会做一次转化,Spring默认支持所有内置类型的转换,如int, long, String, boolean等。
Spring在初始化容器的时候,会校验bean的配置。如果bean的scope是singleton,并且是提前初始化(默认)的,那会随容器一起创建。否则的话只有在使用的时候才会创建。 创建一个bean有可能会让依赖图上的所有bean都创建(如果还没有创建),这里可能会有类型不匹配的风险。
循环依赖
比如A类的构造函数有对B的依赖,B的构造函数有对A的依赖,Spring碰到这种情况会抛BeanCurrentlyInCreationException。
可以不使用构造函数注入,改成setter注入。
依赖与配置
- 基本类型属性
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-
method="close">
<!-- results in a setDriverClassName(String) call -->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="misterkaoli"/>
</bean>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="com.mysql.jdbc.Driver"
p:url="jdbc:mysql://localhost:3306/mydb"
p:username=
"
root
"
p:password="misterkaoli"/>
</beans>
<bean id="mappings"
class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<!-- typed as a java.util.Properties -->
<property name="properties">
<value>
jdbc.driver.className=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb
</value>
</property>
</bean>
- 其他bean
在<constructor-arg/>或者 <property/>里使用ref。
<ref bean="someBean"/>
如果是在父容器里的,那bean要改成parent
<!-- in the parent context -->
<bean id="accountService" class="com.something.SimpleAccountService">
<!-- insert dependencies as required here -->
</bean>
<!-- in the child (descendant) context -->
<bean id="accountService" <!-- bean name is the same as the parent bean -->
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref parent="accountService"/> <!-- notice how we refer to the parent bean -->
</property>
<!-- insert other configuration and dependencies as required here -->
</bean>
- 内部的bean
<bean id="outer" class="...">
<!-- instead of using a reference to a target bean, simply define the target bean
inline -->
<property name="target">
<bean class="com.example.Person"> <!-- this is the inner bean -->
<property name="name" value="Fiona Apple"/>
<property name="age" value="25"/>
</bean>
</property>
</bean>
作为匿名内部类实例处理。
- 集合
<list/>, <set/>, <map/>, and <props/> 对应 List, Set, Map, 和 Properties。
<bean id="moreComplexObject" class="example.ComplexObject">
<!-- results in a setAdminEmails(java.util.Properties) call -->
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.org</prop>
<prop key="support">support@example.org</prop>
<prop key="development">development@example.org</prop>
</props>
</property>
<!-- results in a setSomeList(java.util.List) call -->
<property name="someList">
<list>
<value>a list element followed by a reference</value>
<ref bean="myDataSource" />
</list>
</property>
<!-- results in a setSomeMap(java.util.Map) call -->
<property name="someMap">
<map>
<entry key="an entry" value="just some string"/>
<entry key="a ref" value-ref="myDataSource"/>
</map>
</property>
<!-- results in a setSomeSet(java.util.Set) call -->
<property name="someSet">
<set>
<value>just some string</value>
<ref bean="myDataSource" />
</set>
</property>
</bean>
key value支持以下类型:
bean | ref | idref | list | set | map | props | value | null
容器也支持合并集合,使用parent把parent的选项合并到child里,注意要加上merge=true。
<beans>
<bean id="parent" abstract="true" class="example.ComplexObject">
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.com</prop>
<prop key="support">support@example.com</prop>
</props>
</property>
</bean>
<bean id="child" parent="parent">
<property name="adminEmails">
<!-- the merge is specified on the child collection definition -->
<props merge="true">
<prop key="sales">sales@example.com</prop>
<prop key="support">support@example.co.uk</prop>
</props>
</property>
</bean>
<beans>
- null值与空值
其他的都按照空字符串处理,除非使用
- XML简写 p-namespace
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="classic" class="com.example.ExampleBean">
<property name="email" value="someone@somewhere.com"/>
</bean>
<bean name="p-namespace" class="com.example.ExampleBean"
p:email="someone@somewhere.com"/>
</beans>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="john-classic" class="com.example.Person">
<property name="name" value="John Doe"/>
<property name="spouse" ref="jane"/>
</bean>
<bean name="john-modern"
class="com.example.Person"
p:name="John Doe"
p:spouse-ref="jane"/>
<bean name="jane" class="com.example.Person">
<property name="name" value="Jane Doe"/>
</bean>
</beans>
- XML简写 c-namespace
<!-- c-namespace index declaration -->
<bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree"
c:_2="something@somewhere.com"/>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
<!-- traditional declaration with optional argument names -->
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg name="thingTwo" ref="beanTwo"/>
<constructor-arg name="thingThree" ref="beanThree"/>
<constructor-arg name="email" value="something@somewhere.com"/>
</bean>
<!-- c-namespace declaration with argument names -->
<bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>
</beans>
- 嵌套属性名
<bean id="something" class="things.ThingOne">
<property name="fred.bob.sammy" value="123" />
</bean>
depends-on
大部分情况下,依赖关系通过ref就能表达出来,但也有更隐晦一些的依赖,比如DB driver的初始化。depends-on可以强制在当前bean初始化之前,初始化一些其他bean。如果bean是singleton,那bean的destruction顺序也会被影响。
<!-- 单个强制依赖场景 -->
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
<!-- 多个强制依赖场景 -->
<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
<property name="manager" ref="manager" />
</bean>
<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
延迟初始化
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.something.AnotherBean"/>
<beans default-lazy-init="true">
<!-- no beans will be pre-instantiated... -->
</beans>
Autowiring
使用 的 autowire属性开启。
4种模式
-
- no -- 必须使用ref定义依赖关系
- byName -- 根据名字来
- byType -- 根据类型,如果容器里有多于一个这个类型的bean,会抛错退出
- constructor -- 同byType,区别是只对构造函数的参数有效
排除不被autowire
-
- 把 的autowire-candidate 属性 设置为 false,这个bean就不会作为autowire的candicate管理了。
- 在层,设置default-autowire-candidate,限制范围。比如*Repository限制只有名字以Respostiry结尾的才作为autowire选项。如果多个,用逗号分开。
方法注入
想象一个场景,一个单例bean要调用非单例实例的方法,但是注入只在单例创建的时候,那就不能保证每次调用方法的时候都是新的依赖实例了。
第一个办法是放弃一些IoC功能,每次调用的时候都去容器拿一遍依赖项。但是这就让业务代码与spring深耦合了,不推荐。
// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;
// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class CommandManager implements ApplicationContextAware {
private ApplicationContext applicationContext;
public Object process(Map commandState) {
// grab a new instance of the appropriate Command
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
protected Command createCommand() {
// notice the Spring API dependency!
return this.applicationContext.getBean("command", Command.class);
}
public void setApplicationContext(
ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
- Lookup Method Injection
容器关联的bean的方法,可以被重写,使用容器里另一个bean的方法。另一个bean往往是prototype类型的(非singleton)。这种情况下,Spring框架通过使用CGLIB动态创建子类,在子类里重写该方法实现。
Lookup Method Injection有些限制:
-
- bean的类以及需要被重写的方法不能是final的。
- 单元测试的时候,如果这个方法是abstract的,需要自行stub一个子类和子类方法
- 不能与工厂方法一起使用,尤其是配置类的@Bean方法,那个时候容器还不能动态创建子类。
package fiona.apple;
// no more Spring imports!
public abstract class CommandManager {
public Object process(Object commandState) {
// grab a new instance of the appropriate Command interface
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
// okay... but where is the implementation of this method?
protected abstract Command createCommand();
}
<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
<!-- inject dependencies here as required -->
</bean>
<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
<lookup-method name="createCommand" bean="myCommand"/>
</bean>
public abstract class CommandManager {
public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup("myCommand")
protected abstract Command createCommand();
}
public abstract class CommandManager {
public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup
protected abstract Command createCommand();
}
- Arbitrary Method Replacement
官网说这玩意用不上的,建议别看了。所以我决定听他们的。
就是那些。
Bean Scopes
默认提供以下几个,想的话也可以自定义一个scope。
1. singleton
这里singleton的概念,和GangOfFour的单例模式,有些许区别:
-
- 单例模式: 一个ClassLoader只有一个实例。
- spring:一个容器中的一个bean只能有一个实例(per-container and per-bean)。
无状态的bean设置为singleton。
2. prototype
每次访问这个bean都创建一个对象。适合有状态的bean。
容器并不全程管理prototype类型的bean,只负责初始化、配置和装配,交给使用者(相当于new函数的职责),后续流程就不参与管理了。destruction lifecycle callbacks是不会被调用的。如果有资源需要释放,需要手工处理,可以考虑使自定义bean post-processor。
3. 网络环境的scope
-
- request -- 一个 HTTP request 一个 scope="request", @RequestScope
- session -- 一个 HTTP Session一个 scope="session" @SessionScope
- application -- 一个 ServletContext 一个 scope="application" @ApplicationScope
- websocket -- 一个WebSocket一个
要支持这几个,可能需要做一些动作才行。如果是使用的servlet技术栈(Spring Web MVC + DispatcherServlet),DispatcherServlet已经帮忙处理好了。
如果用的是Servlet 2.5 的容器,或者不使用DispatcherServlet处理请求,需要注册RequestContextListener 和ServletRequestListener。如果是Servlet3.0,使用WebApplicationInitializer接口。
4. c.f. application vs singleton
看着差不多,只不过singleton在ApplicationContext里,application的bean在ServletContext里。
5. 大scope依赖小scope的bean
假设想在singleton的bean里注入一个HTTP请求范围的bean,那最好注入那个bean的AOP代理,并将方法调用代理到实际的bean处理。
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- an HTTP Session-scoped bean exposed as a proxy -->
<bean id="userPreferences" class="com.something.UserPreferences" scope="session">
<!-- instructs the container to proxy the surrounding bean -->
<aop:scoped-proxy/> ①
</bean>
<!-- a singleton-scoped bean injected with a proxy to the above bean -->
<bean id="userService" class="com.something.SimpleUserService">
<!-- a reference to the proxied userPreferences bean -->
<property name="userPreferences" ref="userPreferences"/>
</bean>
</beans>
<!-- DefaultUserPreferences implements the UserPreferences interface -->
<bean id="userPreferences" class="com.stuff.DefaultUserPreferences" scope="session">
<aop:scoped-proxy proxy-target-class="false"/>
</bean>
<bean id="userManager" class="com.stuff.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
@Scope(value = WebApplicationContext.SCOPE_SESSION,
proxyMode =ScopedProxyMode.INTERFACES)
@RequestScope(proxyMode = ScopedProxyMode.TARGET_CLASS)
还能一个线程一个的,但是要手动开启。
6. 自定义Scope
实现接口org.springframework.beans.factory.config.Scope
- 代码方式
调用ConfigurableBeanFactory接口的void registerScope(String scopeName, Scope scope)方法注册Scope实现。ConfigurableBeanFactory在大部分的ApplicationContext实现的BeanFactory属性中都能获得。
Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);
<bean id="..." class="..." scope="thread">
- 声明方式
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="thread">
<bean class="
org.springframework.context.support.SimpleThreadScope"/>
</entry>
</map>
</property>
</bean>
<bean id="thing2" class="x.y.Thing2" scope="thread">
<property name="name" value="Rick"/>
<aop:scoped-proxy/>
</bean>
<bean id="thing1" class="x.y.Thing1">
<property name="thing2" ref="thing2"/>
</bean>
</beans>
Bean的拓展点
生命周期回调方法
例子:
-
- InitializingBean接口的afterPropertiesSet()方法 --初始化
- DisposableBean接口的destroy()方法 -- 销毁前
- @PostConstruct (JSR-250)
- @PreDestroy (JSR-250)
- init-method bean definition metadata
- destroy-method bean definition metadata
Spring内部是使用BeanPostProcessor来调用回调方法的。如果不够用,可以自己写一个。
初始化时期的回调
InitializingBean接口的afterPropertiesSet()方法
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements InitializingBean {
@Override
public void afterPropertiesSet() {
// do some initialization work
}
}
xml中的init-method
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean {
public void init() {
// do some initialization work
}
}
销毁前的回调
DisposableBean接口的destroy()方法
destroy-method bean definition metadata
默认的初始化和销毁方法
default-init-method & default-destroy-method
执行时机是依赖已经注入,但在aop之前。
执行顺序
容器启动与关闭期间的回调
只要一个bean实现了Lifecycle接口(start,stop,isRunning),当容器(ApplicationContext)接收到start或stop信号的时候,就会调用这些bean的相关方法。实现原理是调用 LifecycleProcessor(onRefresh,onClose)。LifecycleProcessor本身也实现了Lifecycle接口。
Lifecycle是比较粗颗粒度的,如果需要细颗粒度的控制,可以考虑实现SmartLifecycle接口。
启动的时候,phase最小的先启动,关闭的时候,phase最大的先关闭。默认是0。
LifecycleProcessor的默认实现类是DefaultLifecycleProcessor。DefaultLifecycleProcessor会等待stop执行结束,但是等待是有超时时间的,默认每个30秒。也可以自己定义一个名字是lifecycleProcessor的bean替换掉默认的。如果只是想更改超时等待时间,设置字段即可:
<bean id="lifecycleProcessor" class=
"
org.springframework.context.support.DefaultLifecycleProcessor">
<!-- timeout value in milliseconds -->
<property name="timeoutPerShutdownPhase" value="10000"/>
</bean>
server:
shutdown: graceful ## 开启优雅停机
spring:
lifecycle:
timeout-per-shutdown-phase: 50s ## 优雅停机等待时间,默认30s
非网络应用优雅关闭容器
网络应用的话,(WebApplicationContext)已经提供了优雅停机支持。如果是非网络应用,需要优雅关闭,需要业务根据需要自己实现。
注册一个shutdownHook
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public final class Boot {
public static void main(final String[] args) throws Exception {
ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext( "beans.xml");
// add a shutdown hook for the above context...
ctx.registerShutdownHook();
// app runs here...
// main method exits, hook is called prior to the app shutting down...
}
}
ApplicationContextAware and BeanNameAware
ApplicationContextAware
如果一个bean实现了ApplicationContextAware接口,在ApplicationContext创建它的时候,会把自己的引用放一个在这个bean里(ApplicationContextAware.setApplicationContext),这个bean就可以拿到创建它的容器,并且做一些事情
-
- 通过容器拿其他bean(不推荐)
- 通过容器拿到文件资源
- 通过容器发布应用事件
- 访问MessageSource
- ......
当然bean获取ApplicationContext也可以把它当做依赖自动注入。
BeanNameAware
如果一个bean实现了BeanNameAware接口,容器会在创建后调用它的setBeanName方法,把bean的name传进去。
其他Aware接口
因为使用这些接口意味着会与Spring深耦合,就不符合IoC风格了,所以Spring官方建议别实现这些接口。
BeanDefinition继承
ApplicationContext里,子BeanDefinition的实现是ChildBeanDefinition。但是实际xml中是用parent标记表达的。
除了以下的属性只拿当前BeanDefinition的,其他的都可以继承
-
- depends on
- autowire mode
- dependency check
- singleton
- lazy init
如果父级的BeanDefinition是abstract,那它就只是作为一个模板存在,不会实例化。
<bean id="inheritedTestBean" abstract="true"
class="org.springframework.beans.TestBean">
<property name="name" value="parent"/>
<property name="age" value="1"/>
</bean>
<bean id="inheritsWithDifferentClass"
class="org.springframework.beans.DerivedTestBean"
parent="inheritedTestBean" init-method="initialize">
①
<property name="name" value="override"/>
<!-- the age property value of 1 will be inherited from parent -->
</bean>
容器的拓展点
一般情况下,业务开发不需要自己实现一个ApplicationContext,可以直接去实现接口提供给容器。
BeanPostProcessor — 拓展bean
可以实现1到多个BeanPostProcessor实例,执行顺序由order属性决定(通过实现Ordered接口来定义)。
容器首先实例化一个bean对象,BeanPostProcessor接着做二次加工。每个BeanPostProcessor实例,都是一个容器一个的,没法跨容器工作的哈(即便是有继承关系的容器)。
如果要有顺序,那就让Spring自己扫描到(需要注意声明的类型不然就是实现类本身,不然就是BeanPostProcessor,否则认不出来是BeanPostProcessor),不要手工代码往容器添加。
因为AOP auto-proxying本身自己就是一个BeanPostProcessor,所以对BeanPostProcessor做切面很可能无效的。
简单的示例代码:
package scripting;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {
// simply return the instantiated bean as-is
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean; // we could potentially return any object reference here...
}
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("Bean '" + beanName + "' created : " + bean.toString());
return bean;
}
}
实际的例子:AutowiredAnnotationBeanPostProcessor
BeanFactoryPostProcessor — 拓展元配置
BeanFactoryPostProcessor处理的是BeanDefinition,并且是在Bean初始化之前,也就是说,它允许改变BeanDefinition。
也可以使用Ordered定义多个BeanFactoryPostProcessor的执行顺序。
不能跨容器干活。
Spring自带的BeanFactoryPostProcessor有:
-
- PropertyOverrideConfigurer
- PropertySourcesPlaceholderConfigurer
使用例子可以看官方文档,代码看源码。
FactoryBean — 拓展实例化逻辑
基于注解的容器配置
对注解的扫描先于xml,所以xml的会覆盖注解的。
使用context:annotation-config/开启注解扫描,它会自动注册这些:
-
- ConfigurationClassPostProcessor
- AutowiredAnnotationBeanPostProcessor
- CommonAnnotationBeanPostProcessor
- PersistenceAnnotationBeanPostProcessor
- EventListenerMethodProcessor
@Required
已经标记为Deprecated了。
可以放在setter方法上,表示这个属性应该在配置时赋值,不然就是从BeanDefinition里拿值,不然就autowire过来一个对应的bean。如果没有的话,容器应该抛错。
Spring更建议使用注解校验,以免容器外场景使用的时候没有校验。
注意,要使用@Required必须注册RequiredAnnotationBeanPostProcessor
@Autowired
将由JSR-330的@Inject替换它的职责
可以加在构造函数上。Spring4.3以后,如果只有一个构造函数,则不需要加。如果有多个,还是需要加上以指定使用哪个实例化Bean。
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
也可以加在setter方法上
也可以加在有参数的方法上
也可以加在属性上
注意是根据类型匹配的,除非添加了required = false, 如果找不到对应类型的bean,会抛运行时错no type
match found。
这样会把所有这个类型的Bean都绑进数组里。默认按照注册顺序。如果想保证顺序,可以在目标bean上加@Order或者@Priority。@Order只影响注入顺序,不影响实例化顺序的哈(实例化顺序要考虑依赖关系)。
如果一个bean有多个构造函数,那其中只有一个可以是@Autowired(required = true)的。其他要么不加,要么required应该为false。
Spring最不推荐使用@Required表达非空。建议可以使用@Autowired(required = true)。
但更推荐使用java.util.Optional:
或者@Nullable(spring也提供了, JSR-305的javax.annotation.Nullable 也提供了)
推荐使用这个注解关联Spring的一些bean:
BeanFactory, ApplicationContext, Environment, ResourceLoader, ApplicationEventPublisher, and
MessageSource, ConfigurableApplicationContext ResourcePatternResolver
注意:@Autowired, @Inject, @Value, @Resource是由BeanPostProcessor实现功能的,所以不能应用在BeanPostProcessor 或者 BeanFactoryPostProcessor实现里。
@Primary
针对@Autowired有多个符合条件的bean的场景,用哪个的问题,Spring提供了@Primary来标注这个bean是首选项。
Qualifiers
除了@Primary,Spring还提供了Qualifier帮助缩小范围。
默认是用名称,也可以自己指定id或者名称
如果想根据bean的名字自动注入,建议使用@Resource而不是@Autowired。
if you intend to express annotation-driven injection by name, do not primarily use
@Autowired, even if it is capable of selecting by bean name among type-matching candidates.
Instead, use the JSR-250 @Resource annotation, which is semantically defined to identify a specific
target component by its unique name, with the declared type being irrelevant for the matching
process. @Autowired has rather different semantics: After selecting candidate beans by type, the
specified String qualifier value is considered within those type-selected candidates only (for
example, matching an account qualifier against beans marked with the same qualifier label).
c.f. @Autowired vs @Resource
-
- @Autowired根据类型,@Resource根据名字
- @Autowired应用范围:字段、构造函数、多参方法
- @Resource应用范围:字段,或者单参setter方法
泛化的Qualifiers
CustomAutowireConfigurer
CustomAutowireConfigurer是Spring提供的BeanFactoryPostProcessor,它允许注册自定义的Qualifier。
@Resource
javax.annotation.Resource是JSR-1504规范里的注解。适用于成员字段和bean的属性setter方法。
有一个name参数,用于定义需要被注入的bean的名字。如果没有定义,就用字段的名字。
对一些内部的常用的依赖如BeanFactory, ApplicationContext, ResourceLoader, ApplicationEventPublisher, 以及 MessageSource。即使名字不一样,也可以根据类型注入进去。
@Value
常用于注入属性值(值来自外部)。
Spring提供了内嵌的值解析器(value resolver),如果找不到对应的值,会把值名称(如${catalog.name})注入。如果想要更严格的控制,可以提供一个自定义的PropertySourcesPlaceholderConfigurer。注意应该是个static方法注入。
@Configuration
public class AppConfig {
@Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer()
{
return new PropertySourcesPlaceholderConfigurer();
}
}
SpringBoot提供了一个支持解析application.properties和application.yml的PropertySourcesPlaceholderConfigurer。
Spring提供了内嵌的转换器,支持简单的类型转换,如int Integer,以及逗号分割的会被拆成字符串数组。背后实现这个功能的是Spring提供了调用ConversionService来做转换的BeanPostProcessor,支持把字符串转为其他类型。也可以自定义一个ConversionService:
@Value也能接收SpEL表达式的。
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }")
String catalog) {
this.catalog = catalog;
}
}
@Component
public class MovieRecommender {
private final Map<String, Integer> countOfMoviesPerCatalog;
public MovieRecommender(
@Value("#{{'Thriller': 100, 'Comedy': 300}}") Map<String, Integer>
countOfMoviesPerCatalog) {
this.countOfMoviesPerCatalog = countOfMoviesPerCatalog;
}
}
@PostConstruct and @PreDestroy
这俩也是JSR-250规范里的。Spring2.5提供了CommonAnnotationBeanPostProcessor支持他们。
@Resource,@PostConstruct和@PreDestroy在JDK 6 to 8都是直接提供的。但是自从JDK9开始,整个javax.annotation都被剥离开,并且在JDK11里直接移除了。需要的话,从maven里加上javax.annotation-api依赖就好了。
Classpath Scanning and Managed Components
前面说的都是通过xml加载元配置,生成BeanDefinition。前面虽然也介绍了一些含有元配置含义的注解,但都是在依赖注入的范围里。至于基础的BeanDefinition还是在xml文件里的。事实上我们也可以使用注解、AspectJ type expressions,或者自定义的筛选条件来确定哪些类需要生成对应的BeanDefinition。
Spring3.0及以后,Spring Framework提供 @Configuration, @Bean,@Import, @DependsOn 来支持注解定义元配置。
模式注解
模式注解用于表达组件在应用中的角色。
-
- @Component 泛化的概念,表示需要被Spring管理的组件,另外仨都是它的细化
- @Repository 持久化,如数据库(Data Access Object or DAO)
- @Service 服务层
- @Controller 展示层
虽然@Component更像万金油,但是把含义分清楚,在需要引入切面的时候,会更好些。
元注解与复合注解
Spring提供的注解里,有很多都是元注解。可以自有组合和封装自己的注解。
自动识别Class并注册BeanDefinition
Spring容器可以自动识别标记了模式注解的类,并且创建对应的BeanDefinition。只需要在标记了@Configuration的类上加上@ComponentScan即可,basePackages是扫描范围,多个的话可以用逗号,分号或者空格分割。
如果开启了component scan的话,会自动开启AutowiredAnnotationBeanPostProcessor 和 CommonAnnotationBeanPostProcessor。如果不想开启的话,把annotation-config设置为false即可。
自定义扫描规则
Spring默认扫描@Component, @Repository, @Service, @Controller, @Configuration,以及标记了其他含有@Component元注解的注解。如果想在这个事情上做手脚,可以自定义筛选器,然后注册到 @ComponentScan的includeFilters或者excludeFilters属性。也可以XML:<context:component-
scan>的<context:include-filter /> 或者 <context:exclude-filter /> 子标签。
每个筛选器,有俩属性,type和expression。
例子:
如果设置useDefaultFilters=false,那所有的默认的都会被关闭。
在Components里定义Bean元数据
之前介绍的都是在@Configuration类里使用@Bean定义bean的元数据。在@Component类里也可以定义bean的元数据,同样是使用@Bean。
@Component
public class FactoryMethodComponent {
@Bean
@Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
public void doWork() {
// Component method implementation omitted
}
}
Bean的元数据信息很多。@Bean只能告诉我们构造是通过这个工厂方法构造的,bean的名字是方法名(或者@Bean注解的值),其他的信息,如Scope,延迟加载,也可以通过注解提供,如@Scope, @Lazy。
@Component
public class FactoryMethodComponent {
private static int i;
@Bean
@Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
// use of a custom qualifier and autowiring of method parameters
@Bean
protected TestBean protectedInstance(
@Qualifier("public") TestBean spouse,
@Value("#{privateInstance.age}") String country) {
TestBean tb = new TestBean("protectedInstance", 1);
tb.setSpouse(spouse);
tb.setCountry(country);
return tb;
}
@Bean
private TestBean privateInstance() {
return new TestBean("privateInstance", i++);
}
@Bean
@RequestScope
public TestBean requestScopedInstance() {
return new TestBean("requestScopedInstance", 3);
}
}
@Lazy除了标记延迟初始化,也可以用在依赖注入的场景@Autowired或者@Inject上,等真正需要调用的是才初始化。不过依赖注入的场景,@Lazy是有限制的,尤其复杂交互和可空依赖(optional dependencies),这俩场景建议使用 ObjectProvider,更强大一点。
Spring4.3开始,可以使用类型为InjectionPoint或者DependencyDescriptor(前者的子类)来获取触发bean实例化的依赖注入点信息。注意它触发时机是实例化的时候,而不是依赖注入的时候,所以对Prototype Scope的bean才有意义。
@Component
public class FactoryMethodComponent {
@Bean @Scope("prototype")
public TestBean prototypeInstance(InjectionPoint injectionPoint) {
return new TestBean("prototypeInstance for " + injectionPoint.getMember());
}
}
Configuration BeanDefinition vs Component BeanDefinition
在@Configuration里的@Bean工厂方法,如果需要其他对象的字段或者方法,会创建那个对象的BeanDefinition的引用,使用的时候会通过容器。这样就能享受Spring提供的生命周期管理和代理功能。但如果是在@Component下的@Bean工厂方法,就是普通的Java语义了。
可以把@Bean工厂方法声明成静态的,这样就不必把它所在的类实例化了。尤其在提供post-processor beans的时候(如BeanFactoryPostProcessor or BeanPostProcessor)。因为这些bean要在容器生命周期的较早时期初始化的,应该要避免它们的初始化触发其他的配置过早执行。
对静态@Bean方法的调用,是不能被AOP增强的,因为CGLIB不能增强静态方法。另外,直接调用@Bean方法的话,就会创建一个全新的对象,也不会纳入Spring管理。
@Bean方法不建议声明为private或者final,其他的随意。
@Bean方法也可以声明在component类的父类或者配置类上,甚至可以是component类实现的接口的default方法上。
一个类可以有多个@Bean方法,多个@Bean方法可以提供同一个bean,根据运行时的依赖就位情况选择。符合条件的依赖参数越多的那个,是最终被选中的。
被扫描Bean的命名
被扫描的bean的命名,由BeanNameGenerator负责。
自定义命名规则
实现接口BeanNameGenerator
@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
// ...
}
如果两个包有同样名字的Component定义了,默认的会抛错命名冲突。可以使用FullyQualifiedAnnotationBeanNameGenerator改成用全限定名。
但还是更推荐在注解里制定好名字。
被扫描Bean的生命周期Scope
@Scope("prototype"),@RequestScope ...
自定义Scope:ScopeMetadataResolver
@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {
// ...
}
注解提供Qualifier元信息
可以在bean定义的地方使用注解提供qualifier元信息(xml的话是使用qualifier或者meta属性):
注意和xml的区别是,注解是作用在类上的,所以如果这个类有多个bean,那都受影响。如果是在xml里配置,可以每个bean提供不同的元信息。
为目标组件创建索引
虽然classpath扫描也不慢,但仍然可以提升,提升方式是在编译期创建一个静态的组件列表。容器如果检测到索引,会直接使用,不再重新扫描。
要创建索引,需要引入maven依赖:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<version>{spring-version}</version>
<optional>true</optional>
</dependency>
</dependencies>
dependencies {
compileOnly "org.springframework:spring-context-indexer:{spring-version}"
}
dependencies {
annotationProcessor "org.springframework:spring-context-indexer:{spring-version}"
}
spring-context-indexer会创建一个META-INF/spring.components 文件,放进jar包。
如果项目只是一部分有索引,一部分没有,那设置spring.index.ignore为true,没有索引部分还是会走扫描。
JSR-330标准的注解
Spring从3.0版本起开始支持JSR-330标准的注解(主要就是依赖注入相关的)。Spring的支持方式是和Spring自己的注解一样一样的。想要使用这些注解的话,首先得加入依赖
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>x
@Inject和@Named--依赖注入
@Inject和@Autowired用法差不多。@Autowired可以用在字段上、方法上,构造函数的参数上,@Inject也都可以。另外还可以把注入点声明成Provider,通过Provider.get()在需要时才获取(适合声明周期scope更小的bena或者懒加载场景)。
import javax.inject.Inject;
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
public void listMovies() {
this.movieFinder.findMovies(...);
// ...
}
}
@Named相当于@Qualifier的作用,用来根据名字确定用注入哪个bean。
java.util.Optional 和 @Nullable也可以和@Inject搭配使用,并且更合适,因为@Inject并没有required参数。
@Named 和 @ManagedBean -- 标准语义下的@Component
语义是完全相同的,区别是这俩不是元注解,所以不能拿他们搭自己的注解哈。
JSR-330标准注解的局限
Java-based Container Configuration
AnnotationConfigApplicationContext
@Configuration类以及它里面的@Bean方法,各自都是单独的BeanDefiniiton。@Component类、@Named类,以及@ManagedBean类也是单独的BeanDifinition。
如果走XML,由ClassPathXmlApplicationContext处理。
走注解, 由AnnotationConfigApplicationContext处理。
- 使用构造函数创建容器
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
- 使用register配置容器
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AppConfig.class, OtherConfig.class);
ctx.register(AdditionalConfig.class);
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
- 开启Component Scanning
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.scan("com.acme");//扫出来
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
}
AnnotationConfigWebApplicationContext
针对网络应用的变种。用它来配置Spring的ContextLoaderListener来监听Servlet, Spring MVC DispatcherServlet, 等等。
- Spring MVC web application等价的web.xml
<web-app>
<!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContext
instead of the default XmlWebApplicationContext -->
<context-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</context-param>
<!-- Configuration locations must consist of one or more comma- or space-delimited
fully-qualified @Configuration classes. Fully-qualified packages may also be
specified for component-scanning -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.acme.AppConfig</param-value>
</context-param>
<!-- Bootstrap the root application context as usual using ContextLoaderListener
-->
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Declare a Spring MVC DispatcherServlet as usual -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-
class>
<!-- Configure DispatcherServlet to use AnnotationConfigWebApplicationContext
instead of the default XmlWebApplicationContext -->
<init-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</init-param>
<!-- Again, config locations must consist of one or more comma- or space-
delimited
158
and fully-qualified @Configuration classes -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.acme.web.MvcConfig</param-value>
</init-param>
</servlet>
<!-- map all requests for /app/* to the dispatcher servlet -->
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
</web-app>
@Bean
使用@Bean告诉容器需要注册一个bean definition,类型是方法的返回值类型,bean名字默认是方法名。方法必须放在受Spring管理的Bean的类里(@Configuraiton,@Component ... ),或者它实现的接口上(默认方法),或者它的父类里。但是如果放接口,那只有它真正实例化了之后,才有可能通过它实际的类型找到它。
Bean之间的依赖
工厂方法的入参表示当前bean创建所需的依赖。
依赖解析的逻辑,和构造函数依赖,是一样的。
生命周期回调
JSR-250的 @PostConstruct , @PreDestroy
Spring生命周期接口:InitializingBean, DisposableBean, or Lifecycle
支持标准的*Aware接口 (如BeanFactoryAware, BeanNameAware, MessageSourceAware, ApplicationContextAware)
XML的 init-method , destroy-method属性
bean的类里的public的close和shutdown方法会被自动注册为销毁回调。如果有这样的方法,但实际不是,注意要加@Bean(destroyMethod="")
- 声明bean别名
- bean的描述
@Configuration
只能作用于类,标注它是bean definitions的来源之一
Lookup Method注入
如果 singleton-scoped bean 依赖了 prototype-scoped bean,可以使用lookup method injection。
其他
两个bean依赖同一个bean,用的几个?
启动时,由@Bean方法提供的bean,都会用CGLIB创建子类实例代理,在代理类里,会先看下容器里是不是已经有对应scope的实例了,没有才会去创建。前提是@Configuration类没有被声明为final,工厂方法没有被声明成final。
复合注解
Spring支持业务开发创建自己的复合注解,以降低配置的复杂度。
@Import
@Configuration
public class ServiceConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
@Configuration
public class RepositoryConfig {
@Bean
public AccountRepository accountRepository(DataSource dataSource) {
return new JdbcAccountRepository(dataSource);
}
}
@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {
@Bean
public DataSource dataSource() {
// return new DataSource
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig
.
class);
// everything wires up across configuration classes...
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
@Configuration @Bean 加条件
@Profile的底层实现是@Conditional。@Conditional表示在注册当前的bean之前,先要用对应的
org.springframework.context.annotation.Condition判断一下要不要注册。Condition的实现类应该提供一个matches方法,返回boolean。
@Profile的Condition实现类是这样的:
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// Read the @Profile annotation attributes
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
return true;
}
}
return false;
}
return true;
}
注解与XML的混合使用
- 以XML为主:
- 以注解为主:
环境抽象
Spring中对环境的抽象是Environment接口,包括两大概念:profiles,proerties。
profile的概念是一堆bean definition的组合,如果这个Profile被激活,就会加载这堆bean definition。反之不加载。
properties指的就是大家理解的属性。来源可以是JVM系统参数,系统环境变量,JNDI,servlet上下文参数,额外的Properties对象,Map对象等。
Bean Definition Profiles
现有测试环境:
生产环境:
注意如果需要使用JNDI的话,要么用JndiLocatorDelegate,要么用InitialContext。尽量不用 JndiObjectFactoryBean,因为它会强制把返回类型转成FactoryBean。
profile里传的可以是简单的名字,也可以是表达式。!,&,|。注意与和或要一起用的话必须加括号,比如production & (us-east | eu-central)。不允许production & us-east | eu-central。
@Profile也可以作为元注解用的:
一个类里可以有俩。
注意一点,如果有多个重载工厂方法,那@Profile只对第一个被加载的有效。
用XML的话长这样:
激活Profile
注意可以同时激活多个Profile的。
手工改Environment
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
修改属性spring.profiles.active
可以通过修改system environment variables, JVM system properties,
servlet context parameters in web.xml, or even as an entry in JNDI (see PropertySource Abstraction).
如:
-Dspring.profiles.active="profile1,profile2"
默认profile
要修改不用default用其他名字做缺省项的话,可以调用Environment的setDefaultProfiles() 方法或者设置
spring.profiles.default属性。
PropertySource抽象
这段代码里,我们问了Spring一个问题,环境里有没有“my-property”属性呀?Environment要回答这个问题,就需要在一组PropertySource对象里搜索一遍。PropertySource是一个KV对形式的对属性的抽象。而StandardEnvironment是Spring提供的,包含了两个PropertySource对象,一个是JVM系统参数(System.getProperties()),另一个是系统的环境变量(System.getenv())。
StandardServletEnvironment在StandardEnvironment的基础上,还包含了其他额外属性源如servlet配置、servlet上下文属性,也可以开启JndiPropertySource。
搜索的过程是有序的。默认的话,系统参数高于环境变量。
对于普通的StandardServletEnvironment,优先级是这样的:
-
ServletConfig parameters
-
ServletContext parameters (web.xml context-param entries)
-
JNDI environment variables (java:comp/env/ entries)
-
JVM system properties (-D command-line arguments)
-
JVM system environment (operating system environment variables)
想要调整顺序的话,手工调用MutablePropertySources提供的方法。
如何自己定义一个PropertySource
ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());
@PropertySource
@PropertySource允许往Environment添加PropertySource 。
@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {
@Autowired
Environment env;
@Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}
@PropertySource里的占位符${…}是其他参数里拿的。
@Configuration
@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
public class AppConfig {
@Autowired
Environment env;
@Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}
注册LoadTimeWeaver
Spring用LoadTimeWeaver在类被加载进JVM的时候去改变类。要开启的话,需要在@Configuration类上加@EnableLoadTimeWeaving注解:
@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}
<beans>
<context:load-time-weaver/>
</beans>
在JPA场景和Load-time Weaving with AspectJ 场景有用。
ApplicationContext的能力
国际化-MessageSource
applicationContext实现了接口MessageSource,所以能够提供i18n能力。Spring还挺了接口HierarchicalMessageSource,来实现层级化的多语言。
-
- String getMessage(String code, Object[] args, String default, Locale loc)
- String getMessage(String code, Object[] args, Locale loc)
- String getMessage(MessaveSourceResolvable resolvable, Locale locale)
在加载容器的时候,它会自动去搜索MessageSource这个bean(名字必须是messageSource)。如果找不到的话,会去父容器里找,如果真的完全找不到,就创建一个空的DelegatingMessageSource。
Spring提供了3个MessageSource实现类:ResourceBundleMessageSource,
ReloadableResourceBundleMessageSource 和 StaticMessageSource。 StaticMessageSource是提供手工添加多语言配置的,所以基本上不会使用。
ResourceBundleMessageSource配置示例:
可以使用MessageSourceAware接口获取MessageSource,所有被容器管理的bean,如果实现了接口MessageSource,都会被注入容器的MessageSource。
Spring的MessageSource是基于Java的ResourceBundle的。所以如果有多个重名的bundle,只会拿第一个,忽略其他的。
ReloadableResourceBundleMessageSource[5]是Spring提供的,允许从任何Spring资源位置(不仅限于根路径),支持热更新多语言资源文件(并且也支持缓存的噢)。
标准事件和自定义事件
标准事件
容器对事件的支持,主要靠ApplicationEvent类和AppliationListener接口,实现是标准的观察者模式。容器里的实现了ApplicationListener的bean,在每次有ApplicationEvent发布到容器的时候,都能被通知到。
从Spring 4.2开始,事件不一定非得是ApplicationEvent的子类,容器会把它包装一下送出来。
Spring提供的标准事件:
-
- ContextRefreshedEvent -- 容器调用refresh()方法的时候。此时所有bean都被加载,post-processors被激活,singleton scoped beans被实例化,容器可以使用。有些容器支持热更新,那refresh可能会有多次(如XmlWebApplicationContext)。
- ContextStartedEvent
-
- ContextStoppedEvent,stop了也可以再次start
-
- ContextClosedEvent -- 触发点,手工close,或者JVM的shutdown钩子。close表示所有bean都会被销毁,生命周期结束了,是不能再次启动的。
-
- RequestHandledEvent 和ServletRequestHandledEvent(实际发出的是ServletRequestHandledEvent) 只在使用了DispatcherServlet的场景下适用,告诉大家请求已经处理完啦。
自定义事件
定义事件
public class BlockedListEvent extends ApplicationEvent {
private final String address;
private final String content;
public BlockedListEvent(Object source, String address, String content) {
super(source);
this.address = address;
this.content = content;
}
// accessor and other methods...
}
发布事件
调用ApplicationEventPublisher的publishEvent()发布事件。通常是通过实现接口ApplicationEventPublisherAware并且把实现类注册为bean。
public class EmailService implements ApplicationEventPublisherAware {
private List<String> blockedList;
private ApplicationEventPublisher publisher;
public void setBlockedList(List<String> blockedList) {
this.blockedList = blockedList;
}
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public void sendEmail(String address, String content) {
if (blockedList.contains(address)) {
publisher.publishEvent(new BlockedListEvent(this, address, content));
return;
}
// send email...
}
}
接收事件
public class BlockedListNotifier implements ApplicationListener<BlockedListEvent> {
private String notificationAddress;
public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}
public void onApplicationEvent(BlockedListEvent event) {
// notify appropriate parties via notificationAddress...
}
}
注意一下,默认的话,listener的处理是同步的哈。也就是说publishEvent()是一个一个通知的,通知了一个,一个处理完了,再下一个。如果不希望这样,可以看下ApplicationEventMulticaster和SimpleApplicationEventMulticaster。
Spring自带的事件实现是一个简单的,给同一个容器内的bean使用的模型。如果更复杂的场景,可以看下Spring Integration[6]。
事件注解
@EventListener 接收事件
如果要发布多个事件,返回类型就改成事件集合。
异步监听
局限:
- 如果监听器抛错,那发出方是无感知的
- 异步监听不能通过声明返回值类型来发出后续事件,需要注入ApplicationEventPublisher手工处理。
监听顺序
使用@Order
泛型事件
比如发布的是EntityCreatedEvent,接收只接收EntityCreatedEvent。
访问底层资源
容器都是ResourceLoader,可以用来加载Resource对象。Resource是对JDK URL类的增强。Resource可以是任何底层资源,包括类的根目录,文件系统,任何可以用标准URL表示的位置。
实现了ResourceLoaderAware的bean,在创建的时候,会传入ResourceLoader(实际就是容器自己),这个bean就可以获取到静态资源了。
应用启动的追踪
容器内置了ApplicationStartup,会搜集StartupStep数据(tag, end),涉及这几个阶段:
-
- 容器声明周期(包扫描,配置文件管理)
- bean的声明周期(初始化,smart initializaiton,post processing)
- 事件处理
Spring还提供了FlightRecorderApplicationStartup,要启用的话,就在Spring刚启动的时候注入它的实例。
BeanFactory
其他
boilerplate 重复但又必须要的代码,模块化代码
【1】 官方文档里,空格用的是white space,理论上,white space是有很多种的,不只是空格,所有不可见字符都算,包括tab、回车等。但是还没有去试过,也还没仔细看源码……就先认为是空格吧~~~~~
【2】规则在java.beans.Introspector.decapitalize里
【3】要求编译时候加上debug flag,或者在构造函数上加注解@ConstructorProperties
package examples;
public class ExampleBean {
// Fields omitted
@ConstructorProperties({"years", "ultimateAnswer"})
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
【4】Java EE的概念,如JSF,JAX-WS,都是SOA时期架构的遗留了,我理解Spring刚出现的时候可能并没有想着完全干掉SOA,只是希望作为补充或者第二选项。所以Spring文档的全篇都挺谦虚的,并且鼓励一定要和Spring解耦的写代码。
【5】看了下ReloadableResourceBundleMessageSource的API,说是可以从其他路径拿,但是似乎指的也是网络服务容器(Tomcat等)的资源路径下。如果走SpringBoot的话,可能也没啥用,因为都被打包进去了。
【6】Spring自己这么想的,我是觉得为啥不用消息队列。