Spring揭秘-笔记

任何技术或事物都有其适用常景。

 

关于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实例化完成后调用的方法。

 

 

 

……

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值