任何技术或事物都有其适用常景。
关于setter注入和构造注入的讨论就算了……没有谁比谁好,只有看谁更适用于某个常景。选择合适的就是最好的。
一、思想
IoC:控制反转是一种思想,将原本客户端的主动创建、直接依赖、事必躬亲,反转过来了:变成了衣来伸手饭来张口(当然这个伸手和张嘴还是必不可少的,不然你就啥都不做,就心想事成啊……你怎么不去成仙呢)
DI:依赖注入是实现控制反转的一种具体技术。只要你张口或者伸手了,我就给你煮饭喂你吃,买衣服来给你穿上……为什么说是一种实现技术呢,可能还有其他实现技术,目前我还不知道……可能以后不用我亲自煮饭了啊,也不用我亲自喂你了,我直接委托给机器人给你做饭喂饭就可以了,,,,,,,
很多人就初始时纠结于这两个概念间的区别,拼命地想啊,就想把他们理清楚、分开来……但是有的资料又说是一样的,有的又说不一样……其实,这个重要吗?除了在面试时那些无聊的面试官会问下这个问题。真的,可能他们自己理解的都是一知半解,我们应该跳出框框来看问题。如果你认为是一样的,那就继续当做是一样的吧;如果你接收的是不一样的概念,那么也请继续,没必要在这里纠结了,后面还要学的东西多着呢,,,,,我这里分开来也只是我自己给自己的解释而已,不必当真。
1.2、注入方式
Martin Fowler的那篇文章中定义的三种依赖注入方式:构造注入、setter方法注入、接口注入。
有必要争执哪种方式最好吗?No!!!!!!why?请看本文第一句话。
构造注入:写法繁琐,但是可以在初始化时即检测是否有bean被注入了,如果没有就当场发飙弄挂JVM,不要让它再启动了,防止以后出现位置的问题。
setter方法注入:写法简单方便,but 可能由于没有注入bean导致在使用时出现NPE。
发现没有,这两种方式的优缺点真好相对。第三种接口注入方式不说也罢,因为没人会这么使用。之前在编码时,如果使用@Autowired这个注解来要注入bean(张嘴、伸手),IDEA就会提示我:什么不要使用这个啦,然后我就去查资料,在那纠结到底怎么回事,告诉我说应该使用constructor injection ,什么balabala之类的理由……我去,我以前还纠结了好久,现在就不纠结了,和我有什么关系,我反正从没用过@Autowired,我用的一直都是@Resource,只是看到别人用的Autowired时会纠结于IDE的提示……
我自己是一个很纠结的人的,但是只有在说服自己之后,跳出原来的框才能解脱,所以我尽量用自己的想法来理解和看待。
没必要非得选择最好的,也没有唯一,选择只有合适的才是最好的。
1.3、相关概念
IoC的职责:1、创建对象(煮饭);2、注入依赖对象(喂饭)。
IoC怎么管理对象间的依赖关系(给谁喂什么饭可不能错了啊,给小孩喂奶,老人喂粥,其他人吃肉):N种方式:
1、文本记录依赖关系:张三要吃肉、李四要喝粥、王五不吃不喝……
2、语音、图像……
任何可以表示关系的且能被IoC方便识别的信息载体都可以用来管理依赖关系,but,毕竟我们还没到那么先进的地步,所以还是老老实实的想些可行的靠谱的方案吧:
1、编程式:在代码中将依赖关系写好;
2、文本配置:xml或者properties,格式随便自己定,只要写出对应的解析器就可以了;
3、注解方式:最后其实也是编程实现的;
二、Spring的设计
Spring的设计开始了:
既然强调的是基于POJO(相较于EJB说的好了很多,虽然我不清楚EJB是个什么东西……),那么就是管理所有的bean(也别问这个bean是什么含义,没有含义,就这么叫!所有的对象都是一个bean),所以定义了这么一个接口BeanFactory用来管理所有的bean。
2.1、BeanFactory
BeanFactory管理了所有的bean之后,如果需要bean对象(伸手张嘴)就是从中获取了,上面的那么多getBean方法看到了吧,就是用来干这事的。至于怎么将bean创建出来,注入进去,这个就得拆开来慢慢设计了。
这个是原型代码:只显示了获取bean,创建及注入的事后面再说
BeanFactory container = new XmlBeanFactory(new ClassPathResource("配置文件路径"));
FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("djNewsProvider");
newsProvider.getAndPersistNews();
BeanFactory里面就12个方法,且还都是获取bean和is判断的方法,根本就没有注册对象并管理依赖的功能。那么bean是怎么进去的呢?
2.2、BeanDefinitionRegistry
这里就是一个设计思想了:BeanFactory相当于一个对外的窗口(类似于facade模式),只提供查询的方法,而具体的事由其他的专业类去完成。这就出现了这个BeanDefinitionRegistry,BeanDefinitionRegistry才是真正存放对象的地方,BeanFactory只是一个对外暴露出来的接口。
2.3、BeanDefinition
按照面向对象的思想,每一类相同的事物都应该抽取出一个类来,因此在Spring的IoC容器中管理的POJO也抽取了一个共同类:BeanDefinition,每一个BeanDefinition的实例都表示一个被注入的对象,定义了一些bean的class类型信息、构造方法参数信息等。
BeanDefinitionRegistry就是用来操作BeanDefinition的。BeanDefinition主要有两个实现类:RootBeanDefinition和ChildBeanDefinition
可以上一段代码来看看怎么使用的:
@Test
public void testCodeBeanRegister() {
DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();
BeanFactory container = bindViaCode(beanRegistry);
FXNewsProvider newsProvider = (FXNewsProvider) container.getBean("djNewsProvider");
newsProvider.getAndPersistNews();
}
public static BeanFactory bindViaCode(BeanDefinitionRegistry registry) {
AbstractBeanDefinition newsProvider = new RootBeanDefinition(FXNewsProvider.class);
AbstractBeanDefinition newsListener = new RootBeanDefinition(DowJonesNewsListener.class);
AbstractBeanDefinition newsPersister = new RootBeanDefinition(DowJonesNewsPersister.class);
// 将bean定义注册到容器中
registry.registerBeanDefinition("djNewsProvider", newsProvider);
registry.registerBeanDefinition("djListener", newsListener);
registry.registerBeanDefinition("djPersister", newsPersister);
// 指定依赖关系
// 1. 可以通过构造方法注入方式
ConstructorArgumentValues argValues = new ConstructorArgumentValues();
argValues.addIndexedArgumentValue(0, newsListener);
argValues.addIndexedArgumentValue(1, newsPersister);
newsProvider.setConstructorArgumentValues(argValues);
// 2. 或者通过setter方法注入方式
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.addPropertyValue(new PropertyValue("newsListener", newsListener));
propertyValues.addPropertyValue(new PropertyValue("newPersistener", newsPersister));
newsProvider.setPropertyValues(propertyValues);
// 绑定完成
return (BeanFactory) registry;
}
因此上面这段代码就是定义bean容器,包装bean对象成BeanDefinition,然后将其注入到BeanDefinitionRegistry中,最后就可以从DefaultListableBeanFactory中获取bean了。
2.4、DefaultListableBeanFactory
但是这里的DefaultListableBeanFactory为什么当做BeanDefinitionRegistry来用了呢?因为DefaultListableBeanFactory既实现了BeanFactory又实现了BeanDefinitionRegistry,可以来看一下依赖关系的类图:
绿色的虚线表示实现implement(中间可能有很多abstract的class省略了,包括直接实现和间接实现),灰色的虚线表示关联关系(即拥有某个对象)。不熟悉的可以去复习下UML哈。
DefaultListableBeanFactory内部是用Map来存储beanname和beanDefinition对应关系的:
/** Map of bean definition objects, keyed by bean name */
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(256);
2.5、编码方式实现bean的创建和依赖注入 && 配置文件方式实现的bean创建和依赖注入
理解上面的编码方式实现bean的创建和依赖注入是必须的,因为不管换成什么其他方式,最后都是落实到编码实现上的。下面再来看看配置文件方式实现的bean创建和依赖管理方式:
也是类似的思路:将具体的事扔给具体的人来处理(外包?),配置文件的读取解析有专门的类来处理,首先还是定义接口了:BeanDefinitionReader,由BeanDefinitionReader相应的实现类读取配置文件并创建BeanDefinition,然后将BeanDefinition注册到BeanDefinitionRegistry中。
伪代码:
BeanDefinitionRegistry beanRegistry = <某个BeanDefinitionRegistry实现类,通常为DefaultListableBeanFactory>;
BeanDefinitionReader beanDefinitionReader = new BeanDefinitionReaderImpl(beanRegistry);
beanDefinitionReader.loadBeanDefinitions("配置文件路径");
// 现在我们就取得了一个可用的BeanDefinitionRegistry实例
Spring提供了PropertiesBeanDefinitionReader用于读取properties配置文件来注册bean,配置文件的格式有其自定义的要求,不过我是从来没用过的,因为一直用的都是xml文件配置,对于xml配置格式的Spring提供的实现类是XmlBeanDefinitionReader,这个具体的配置格式就不说了,无非就是那些scheme约束文件中的元素定义而已,什么<beans><bean> 之类的了……这个具体去看spring的scheme约束文档吧。如果不想用xml配置方式,想要创新?那也可以自定义一种配置方式来管理依赖关系(至于是文件还是语音,那就看你的心情了,记得要实现BeanDefinitionReader就好……)
加载xml配置文件的BeanFactory的使用:
public static void main(String[] args)
{
DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();
BeanFactory container = bindViaXMLFile(beanRegistry);
FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("djNewsProvider");
newsProvider.getAndPersistNews();
}
public static BeanFactory bindViaXMLFile
(BeanDefinitionRegistry registry)
{
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(registry);
reader.loadBeanDefinitions("classpath:../ news-config.xml");
return (BeanFactory)registry;
// 或者直接
//return new XmlBeanFactory(new ClassPathResource("../news-config.xml"));
}
第三个就是使用注解方式来实现的依赖注入了:@Resource、@Autowired、@Controller之类的,以前需要在xml文件中指定这个配置:<context:component-scan base-package="com.xxx.xxx……">
三、Spring容器的具体实现
两阶段:容器启动、bean实例化
3.1、容器启动阶段
通过BeanDefinitionReader加载配置文件,解析,创建相应的BeanDefinition对象,注册到BeanDefinitionRegistry。
3.1.1、插手容器的启动
Spring提供了一种BeanFactoryPostProcessor的容器扩展机制,在容器注册好了BeanDefinition之后,在bean实例化之前,允许我们对BeanDefinition修改。
实现:实现接口BeanFactoryPostProcessor,并通过Ordered接口指定顺序,将此Processor注册到BeanFactory中。
Spring提供了几个现成的BeanFactoryPostProcessor实现类,很少需要自己去写吧,除非有此业务需求。
PropertyPlaceholderConfigurer:修改占位符的数据,在xml中使用了占位符${username.name}之后BeanFactory在加载完所有配置后,BeanDefinition中保存的仍然是占位符的信息,当PropertyPlaceholderConfigurer被作为BeanFactoryPostProcessor应用后,才会使用properties配置文件中的配置信息来替换掉BeanDefinition中占位符所代表的属性值。
PropertyPlaceholderConfigurer同时也会从System类的Properties中查找数据来替换,可以配置是否使用System的配置信息。
应用:可以使用加密的DB账号密码,然后在PropertyPlaceholderConfigurer中解密此字符,给DBpool注入真实的账户密码信息。
PropertyOverrideConfigurer:这个没什么好说的,就是使用properties中的信息来替换掉原来的数据。多个只会保留最后一个。
使用示例代码:
// 声明将被后处理的BeanFactory实例
ConfigurableListableBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("..."));
// 声明要使用的BeanFactoryPostProcessor
PropertyPlaceholderConfigurer propertyPostProcessor = new PropertyPlaceholderConfigurer();
propertyPostProcessor.setLocation(new ClassPathResource("..."));
// 执行后处理操作
propertyPostProcessor.postProcessBeanFactory(beanFactory);
CustomEditorConfigurer:对BeanDefinition不做改变,只是辅助使用的:因为xml中的都是字符串,所以需要将此字符串正确的给对象赋值,就需要转换,就需要CustomEditorConfigurer来做。Spring内部通过PropertyEditor来帮助进行string到具体类型的转换,只要每种对象类型都提供一个PropertyEditor,Spring就可以根据该类型来获取其对应的PropertyEditor然后做具体的转换。Spring已经提供了一些类型转换的实现:StringArrayPropertyEditor(逗号,分隔的字符串转数组)、ClassEditor、FileEditor、LocaleEditor、PatternEditor、、、、、这些PropertyEditor Spring都会默认加载,如果是自定义的,那需要自己注入到容器中去。
1、自定义PropertyEditor,继承PropertyEditorSupport(为了简化不必要实现PropertyEditor所有的方法),
2、将自定义的PropertyEditor注册到容器中,
public class DatePropertyEditor extends PropertyEditorSupport {
private String datePattern;
@Override
public void setAsText(String text) throws IllegalArgumentException {
DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(getDatePattern());
Date dateValue = dateTimeFormatter.parseDateTime(text).toDate();
setValue(dateValue);
}
public String getDatePattern() {
return datePattern;
}
public void setDatePattern(String datePattern) {
this.datePattern = datePattern;
}
}
XmlBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("..."));
//
CustomEditorConfigurer ceConfigurer = new CustomEditorConfigurer();
Map customerEditors = new HashMap();
customerEditors.put(java.util.Date.class,new DatePropertyEditor());
ceConfigurer.setCustomEditors(customerEditors);
//
ceConfigurer.postProcessBeanFactory(beanFactory);
使用:
public class DateFoo {
private Date date;
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
}
配置类似于
<bean id="dateFoo" class="...DateFoo">
<property name="date">
<value>2007/10/16</value>
</property>
</bean>
在ApplicationContext中可以使用xml来注册bean。
3.2、bean实例化阶段
当请求getBean时,即实例化bean对象。ApplicationContext是在refresh()方法里立即实例化了所有的bean。
Spring使用策略模式来决定采用何种方式实现bean的初始化。反射直接生成bean还是cglib生成其子类。
策略接口:InstantiationStrategy,实现类:SimpleInstantiationStrategy(以反射方式实现的)、CglibSubclassingInstantiationStrategy(extends SimpleInstantiationStrategy 同时又添加了cglib的方式),Spring默认使用的是CglibSubclassingInstantiationStrategy实现类。
Spring并不是直接返回的bean,而是以BeanWrapper包裹了bean实例后的对象。
BeanWrapper:继承了PropertyAccessor ,可以以统一的方式来访问对象属性。BeanWrapper定义同时又直接或者间接继承了PropertyEditorRegistry和TypeConverter接口。当把各种PropertyEditor注册给容器时,就会被BeanWrapper用到!在第一步构造完成对象之后,Spring会根据对象实例构造一个BeanWrapperImpl实例,然后将之前CustomEditorConfigurer注册的PropertyEditor复制一份给BeanWrapperImpl实例(这就是BeanWrapper同时又是PropertyEditorRegistry的原因)。
使用BeanWrapper操作bean:
Object provider = Class.forName("package.name.FXNewsProvider").newInstance();
Object listener = Class.forName("package.name.DowJonesNewsListener").newInstance();
Object persister = Class.forName("package.name.DowJonesNewsPersister").newInstance();
BeanWrapper newsProvider = new BeanWrapperImpl(provider);
newsProvider.setPropertyValue("newsListener", listener);
newsProvider.setPropertyValue("newPersistener", persister);
assertTrue(newsProvider.getWrappedInstance() instanceof FXNewsProvider);
assertSame(provider, newsProvider.getWrappedInstance());
assertSame(listener, newsProvider.getPropertyValue("newsListener"));
assertSame(persister, newsProvider.getPropertyValue("newPersistener"));
各个Aware接口:只要实现了以这个Aware命名结尾的接口,那么Spring就会给其注入相应的对象。
BeanNameAware:
BeanClassLoaderAware:
BeanFactoryAware:
ResourceLoaderAware:
ApplicationContextAware:
ApplicationEventPublisherAware:
MessageSourceAware:
BeanPostProcessor:上图中的前置处理和后置处理。使用:Aware各种接口的实现类就是通过BeanPostProcessor来实现的,当ApplicationContext中每个对象的实例化过程走到BeanPost- Processor前置处理这一步时,ApplicationContext容器会检测到之前注册到容器的Application-ContextAwareProcessor这个BeanPostProcessor的实现类,然后就会调用其postProcessBefore- Initialization()方法,检查并设置Aware相关依赖。Spring的AOP则更多地使用BeanPostProcessor来为对象生成相应的代理对象。
自定义BeanPostProcessor:先定义,然后注册到容器中,
ConfigurableBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource(...));
beanFactory.addBeanPostProcessor(new PasswordDecodePostProcessor());
...
// getBean();
当然xml配置方式有其格式,上面所有的代码配置方式,最后真的是用都是用xml配置的,没见过使用BeanFactory编码实现注入的。
InitializingBean和init-method:在bean实例化完成后调用的方法。
……