前言
几个月前阅读spring文档时做的笔记,记录了以写我认为重要的内容.
container magicIOC container
IOC(Inverse of Control) 控制反转,也称为DI(Dependency Inject)依赖注入
一个对象定义它所需要的依赖,IOC容器会在对象创建时将这些依赖注入.
举个例子 如果没有IOC,对于依赖我们可能这样处理
public class A{
private B b;
public A(){
this.b=new B();
}
}
现实情况会更复杂,B可能还有自己的依赖,如果B的实例化过程发生变化,所有对B有依赖的对象都要做修改.更麻烦的是,如果A,B有相同的依赖,或者形成的循环依赖(有一部分循环依赖IOC也无法处理,但是有很大一部分通过IOC根本不会形成循环依赖),依赖关系会更无法处理.
有了IOC就会变成这样
public class A{
@Autowired
private B b;
}
A只需要声明它需要的依赖,IOC容器会在A实例化时自动注入相关的依赖.如果B发生变化,A无需改动.这里体现了IOC的一个重要作用解耦
org.springframework.beans 和 org.springframework.context
上面两个包是IOC容器的基础,BeanFactor提供了配置和管理功能,ApplicationContext在BeanFactory的基础上进一步封装,添加了一些企业级特性,更易集成使用
什么是bean
在Spring中,构成应用骨架的且由IOC Container进行管理的那些Object称为bean.一个bean就是一个由IOC Container进行实例化,装配且管理的Object
Container 概述
ApplicationContext
org.springframework.context.ApplicationContext 接口代表container,负责bean的实例化,配置和装配.
container通过读取configuration metadata获取实例化,配置和装配bean的说明,configuration metadata由xml文件,注解及java代码表示.
Spring 提供了两个开箱即用的ApplicationContext实现, ClassPathXmlApplicationContext和FileSystemXmlApplicationContext
Configuration metadata
configuration metadata 是用户(你)以开发者的角度提供给Spring的数据,告诉Spring应该怎样去处理bean.
主要有三种类型的metadata
- XML-Based
- Annotation-Based
- Java-Based @Configuration @Bean @Import @DependsOn
对于XML,顶级标签<beans>下的每个<bean>元素表示一个bean definition
对于注解,含有@Configuration的类中含有@Bean的方法都是一个bean definition
使用import元素来整合多个配置文件(相对路径),Spring官方不推荐"../service.xml"这可能导致依赖
<beans>
<import resource="services.xml"/>
<import resource="resources/messageSource.xml"/>
<import resource="/resources/themeSource.xml"/>
<bean id="bean1" class="..."/>
<bean id="bean2" class="..."/>
</beans>
基于groovy的bean definition,看起来更加方便
beans {
dataSource(BasicDataSource) {
driverClassName = "org.hsqldb.jdbcDriver"
url = "jdbc:hsqldb:mem:grailsDB"
username = "sa"
password = ""
settings = [mynew:"setting"]
}
sessionFactory(SessionFactory) {
dataSource = dataSource
}
myService(MyService) {
nestedBean = { AnotherBean bean ->
dataSource = dataSource
}
}
}
容器的启动
基于groovy的启动
ApplicationContext context = new GenericGroovyApplicationContext("services.groovy", "daos.groovy");
GeneralApplicationContext使用最灵活,也最麻烦,需要关注细节,如下
GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();
beanDefinition
如果没有提供命名,默认以下面这种方式命名:bean names start with a lowercase letter, and are camel-cased from then on
class | Instantiating beans |
---|---|
name | Naming beans |
scope | Bean scopes |
constructor arguments | Dependency Injection |
properties | Dependency Injection |
autowiring mode | Autowiring collaborators |
lazy-initialization mode | Lazy-initialized beans |
initialization method | Initialization callbacks |
destruction method | Destruction callbacks |
基于xml的实例化
- 通过构造器实例化
<bean id="exampleBean" class="examples.ExampleBean"/>
<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
- 通过静态工厂方法实例化
<bean id="clientService"
class="examples.ClientService"
factory-method="createInstance"/>
- 通过工厂方法实例化
<!-- 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"/>
DI
- 构造器注入
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on a MovieFinder
private MovieFinder movieFinder;
// a constructor so that the Spring container can inject a MovieFinder
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}
- 构造器注入不同类型的多个参数
<beans>
<bean id="foo" class="x.y.Foo">
<constructor-arg ref="bar"/>
<constructor-arg ref="baz"/>
</bean>
<bean id="bar" class="x.y.Bar"/>
<bean id="baz" class="x.y.Baz"/>
</beans>
- 构造器注入多个不同简单类型
<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>
- 构造器注入时指定名称(需要在编译时开启debug)
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>
- 作为前者的替代,使用java annotation
package examples;
public class ExampleBean {
// Fields omitted
@ConstructorProperties({"years", "ultimateAnswer"})
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
构造器注入vs Setter注入
- 构造器注入强制依赖不为空,并且可以保证bean在装配完成后不会再发生变化.
- 原始的setter注入用来注入那些非必须的依赖,@Required注入可以使其变为强制依赖
- 按照目前情况,setter注入更为实用,尤其是@Autowired注解
依赖解析过程
- ApplicationContext通过读取metadata完成创建和初始化
- 对于所有bean,它的依赖都是通过属性,构造器参数,静态工厂方法参数等,当bean真正被创建时(对于 scope为singleton的bean来说,立刻创建),这些依赖被注入
- 每个属性和构造器参数都是一个要被设置的值或是容器中其他bean的引用
- 原始类型的属性被会自动转化,例如将String转为int,long double等等.
使用setter注入取代构造器注入解决循环依赖问题??
ApplicationContext 默认使用 pre-instantiate初始化singleton的bean,也可以更改为lazy-initialize,如果一个bean到运行很长时间后才被请求到,这时候去创建时,很可能出现异常.Spring采用以启动时间和内存换去运行安全的策略
idref标签
用以传递值(而非ref),主要目的是提供部署时检查,通常用在
*A common place (at least in versions earlier than Spring 2.0) where the <idref/>element brings value is in the configuration of AOP interceptors in a ProxyFactoryBean bean definition. Using <idref/> elements when you specify the interceptor names prevents you from misspelling an interceptor id. *
<bean id="theTargetBean" class="..."/>
<bean id="theClientBean" class="...">
<property name="targetName">
<idref bean="theTargetBean"/>
</property>
</bean>
作用和下面相同
<bean id="theTargetBean" class="..." />
<bean id="client" class="...">
<property name="targetName" value="theTargetBean"/>
</bean>
idref的local标签在beans xsd 4.0已被废弃
容器支持
<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>
map或set中的值可以为以下几种
bean | ref | idref | list | set | map | props | value | null
继承时集合的融合和覆盖
<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>
对于list有一点特殊,他有顺序的概念,父类优于子类
对集合中空字符串和null的处理
<bean class="ExampleBean">
<property name="email" value=""/>
</bean>
exampleBean.setEmail("");
<bean class="ExampleBean">
<property name="email">
<null/>
</property>
</bean>
exampleBean.setEmail(null);
复合属性要求路径上的对象不能为空, fred,bob都不能为空,否则nullpointer异常
<bean id="foo" class="foo.Bar">
<property name="fred.bob.sammy" value="123" />
</bean>
depends on
对于关联较小且由实例化顺序要求的两个bean,通过 depends on实现强制指定顺序处理
<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" />
对于singleton的bean,depends on 还有控制 destory 顺序的作用
lazy-initializad
在第一次请求到时才初始化,不推荐,ApplicationContext的默认策略是pre-instantiation
<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.foo.AnotherBean"/>
控制全局的初始化策略
<beans default-lazy-init="true">
<!-- no beans will be pre-instantiated... -->
</beans>
基于 xml的 autowired
不推荐 限制太多
方法注入
场景, 一个singleton的bean需要依赖于一个prototype的bean,或相反
有三个解决方案
- 通过实现 ApplicationContextAware 接口获取ApplicationContext,再用来实现自定义功能
// 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的xml形式(通过cglib生成子类完成)
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>
- 通过lookup的注解形式,最方便,推荐
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) {
MyCommand command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup
protected abstract MyCommand createCommand();
}
注意
- 该类不能是final,被覆盖的方法也不能是final
- 无法于工厂方法一起工作,对于@Configuration的@Bean也是无法工作的
- 单元测试需要自己去stub一个子类
Scope
singleton
每个容器中只会有一个singleton的bean,Spring的singleton定义为per container and per bean
prototype
每次向容器请求时都会生成一个新的Object,与其它scope的类型最大的区别就是它的回收需要客户端自己去处理(可以通过beanPostProcessor处理)
long-live bean中注入short-live 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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- an HTTP Session-scoped bean exposed as a proxy -->
<bean id="userPreferences" class="com.foo.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.foo.SimpleUserService">
<!-- a reference to the proxied userPreferences bean -->
<property name="userPreferences" ref="userPreferences"/>
</bean>
</beans>
上面的例子中,userPreferences有<apo:scoped-proxy/>,这说明他是一个被代理的对象,每次userService调用到它时,实际是调用了代理,代理通过自身逻辑获取对应的session-scoped的bean,再执行真实对象的操作(默认使用的CGLIB)
自定义Scope
实现 org.springframework.beans.factory.config.Scope 实现自定义scope
Spring 拓展点
BeanPostProcessor
If you want to implement some custom logic after the Spring container finishes instantiating, configuring, and initializing a bean, you can plug in one or more BeanPostProcessor
implementations.
对spring中解决bean的依赖并且初始化后的操作,可以使用BeanPostProcessor
- 在Spring调用afterPropertiesSet或initMethod之前, beanPostProcessor会被调用
- 在Spring调用initMethod之后,BeanPostProcessor会被调用
ApplicationContext会检查BeanDefinition,对实现了BeanPostProcessor的bean,将其注册为BeanPostProcessor
作为BeanPostProcessor的bean是不会被BeanPostProcessor处理的
BeanFactoryPostProcessor
-
PropertyPlaceholderConfigurer 实现参数值替换
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations" value="classpath:com/foo/jdbc.properties"/> </bean> <bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean>
-
PropertyPlaceholderConfigurer
实现类名替换
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<value>classpath:com/foo/strategy.properties</value>
</property>
<property name="properties">
<value>custom.strategy.class=com.foo.DefaultStrategy</value>
</property>
</bean>
<bean id="serviceStrategy" class="${custom.strategy.class}"/>
- PropertyOverrideConfigurer 覆盖默认值
<context:property-override location="classpath:override.properties"/>
FactoryBean 获取制造bean的bean
FactoryBean有三个方法
- Object getObject()
- boolean isSingleton()
- boolean isSingleton()
ApplicationContext.getBean("$beanName")可以获取FactoryBean
lifeCycle
afterPropertiesSet和PostConstruct
在Spring容器完成必要的依赖注入后,会调用该方法完成初始化,此时所有的BeanPostProcessor还未执行
<!--两个作用相同,约定的力量-->
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
//这两个作用也相同,但是更推荐前者
public class ExampleBean {
public void init() {
// do some initialization work
}
}
public class AnotherExampleBean implements InitializingBean {
public void afterPropertiesSet() {
// do some initialization work
}
}
destroy和@PreDestroy
在Spring容器销毁前会调用该方法
<!--两者作用相同-->
<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class ExampleBean {
public void cleanup() {
// do some destruction work (like releasing pooled connections)
}
}
public class AnotherExampleBean implements DisposableBean {
public void destroy() {
// do some destruction work (like releasing pooled connections)
}
}
*实现了java.lang.AutoCloseable
or java.io.Closeable的bean,可以让Spring自动检测它的close/shutdown方法
*
如果同时定义个多个 lifecycle,会按照 annotation interface xml的顺序执行,并且同名的只执行一次
Startup and shutdown callbacks
stop不能保证
public interface Lifecycle {
void start();
void stop();
boolean isRunning();
}
public interface LifecycleProcessor extends Lifecycle {
void onRefresh();
void onClose();
}
Context refresh :所有bean都被实例化和初始化后
@Autowired
, @Inject
, @Resource
, and @Value
annotations are handled by Spring BeanPostProcessor
implementations
@Autowired和@Resource的区别
@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 will be considered within those type-selected candidates only, e.g. matching an "account" qualifier against beans marked with the same qualifier label.
CustomAutowireConfigurer
也是beanPostProcessor,用来处理自定义注解(如@Qualify)
@Autowired的匹配过程
先匹配名字的customerPreferenceDao的bean,再匹配类型是CustomerPreferenceDao的bean
public class MovieRecommender {
@Resource
private CustomerPreferenceDao customerPreferenceDao;
@Resource
private ApplicationContext context;
public MovieRecommender() {
}
// ...
}
CommonAnnotationBeanPostProcessor
处理@Resource和与生命周期相关的bean
@ComponentScan(basePackages = "org.example")
完全的java config,与之对应的xml为
<context:component-scan base-package="org.example"/>
the AutowiredAnnotationBeanPostProcessor
and CommonAnnotationBeanPostProcessor
are both included implicitly when you use the component-scan element.
使用component-scan后会自动包含 AutowiredAnnotationBeanPostProcessor
和CommonAnnotationBeanPostProcessor
在package-scan的基础上包含或去处class
@Configuration
@ComponentScan(basePackages = "org.example",
includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
excludeFilters = @Filter(Repository.class))
public class AppConfig {
...
}
#{ expression }
实现BeanNameGenerator实现自定义bean命名
@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
...
}
<beans>
<context:component-scan base-package="org.example"
name-generator="org.example.MyNameGenerator" />
</beans>
实现 ScopeMetadataResolver接口实现自定义Scope解析
@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {
...
}
<beans>
<context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver"/>
</beans>
@Profile
使用Profile时尽量不要使用重载,可能导致十分奇怪的问题
激活profile有一下方式
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
-Dspring.profiles.active="profile1,profile2"
设置默认的profile
setDefaultProfiles()
spring.profiles.default
网友评论