轻量级框架Spring的管理代码耦合之道

来源:互联网 发布:淘宝网钻石展位 编辑:程序博客网 时间:2024/06/04 20:12

一、Spring的核心思想

说到Spring的核心,就要从程序中的“耦合”说起。Spring最初就是为了解决程序中耦合的问题。

在面向对象的程序语言中,对象是基本组成单元。我们一直注重降低代码耦合,但要知道,程序就是由不同的对象相互协作而成,所以必然会耦合,完全没有耦合的代码什么也做不了。然而过于耦合的代码尽管可以实现一时的功能,却不利于“自身的成长”,所以需要想个办法去管理耦合。

Spring的核心就是通过 DI(依赖注入)和 IOC(控制反转)管理耦合。注意,并不是消除耦合,而是管理耦合。

(1)直接耦合

这里写图片描述

直接耦合是最简单明了的方式,比如在对象的方法中直接使用另一个对象,或者对象的属性直接引用另一个对象。这种情况,对象自己是明确知道它所依赖的对象的。

(2)间接耦合

这里写图片描述

间接耦合就是在对象的之间再抽象出一层“容器”,对象自己并不知道它依赖的具体对象,而是由“容器”组织起对象间的耦合关系。在Spring中,这个“容器”就是 Context组件,被装进容器的对象已然变成了一个个Bean组件,而发现、建立和维护每个Bean间的关系所需要的一系列工具就是Core组件。这三个组件也正是Spring的三个核心组件。

在程序世界中有三个关注点:(1)数据结构 (2)生存环境 (3)运动。
在Java语言中,对象就是数据结构,对象所处的JVM就是其生存环境,而对象的新建和垃圾回收,以及各种程序交互规则就是运动,由此而诞生出无限可能的程序。
Spring框架的设计理念与之也十分类似,Spring中的数据结构是包装了对象的Bean,Bean所处的生存环境就是Context,而Core则制定了Bean在Context中的运动规律。

多用组合,少用继承原则:
(1)在Spring中,无论是构造器注入还是Setter方法注入,对象之间的关系都是组合。组合具有比继承更好的灵活性和运行时扩展性。
(2)除此之外,你的对象与Spring的关系也是组合——像Structs框架,它会强迫你实现其规范的接口或继承其规范的类,这样就将你的对象与框架捆绑在一起,离开了Structs框架你的对象就无法编译运行。而Spring只需通过配置(通过xml配置或通过注解配置)将你的对象和Spring联系起来,你的对象不需要为Spring作任何改动。

这就是作为轻量级框架的Spring的非侵入式编程模型

二、Bean的创建和装配

Bean的创建就是将你的对象转换为Spring中的Bean的过程,Bean的装配就是建立起Bean之间依赖关系的过程。

2.1、自动化装配Bean

(1)自动扫描

扫描的目的就是自动发现并创建Bean

@Component  //默认的beanId是将第一个字母变为小写的类名@Component("beanId") @Configuration@ComponentScan  //默认将该配置类所在的包作为组件扫描的基础包@ComponentScan("packageName")@ComponentScan(basePackageClasses={Animal.Class,Person.Class})  //将Animal类和Person类所在的包作为组件扫描的基础包

也可用下面的xml配置替代@ComponentScan

<context:component-scan base-package="packageName" />

(2)自动装配

@Autowired@Autowired(required=false)

该注解可用在构造器上、Setter方法上和对象属性上。但需注意:

  1. 只可用于实例属性和方法,不可用于静态属性和方法
  2. 只当有且仅有一个bean匹配的情况下才会被装配进来
  3. 自动装配并不在意要装配的bean来自哪,不管它是声明在Java代码中,还是声明在XML中,亦或是通过自动扫描创建的

(3)处理自动装配的歧义性

//方法一:为bean添加更高的优先级@Component@Primary//方法二:使用限定符@Component@Qualifier("iceCream")@Autowird@Qualifier("iceCream")//方法三:自定义限定符(优点:可以使用多个限定符)@Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})  @Retention(RetentionPolicy.RUNTIME)  @Documented  @Qualifier  public @interface IceCream{  }@Component@IceCream@Autowird@IceCream

2.2、通过Java代码装配Bean

(1)创建bean

@Configurationpublic class BeanConfig{    @Bean(name="beanId")  //默认的beanId是带有该注解的方法名    public Animal animal(){        return new Animal();    }}

(2)装配bean

/* *方法一:手动装配 */@Configurationpublic class BeanConfig{    @Bean    public Animal animal_constructorWiring(){        return new Animal(flyStrategy()); //通过调用同样被@Bean注解的方法,实现构造器注入    }    @Bean    public Animal animal_setterWiring(){        Animal a = new Animal();        a.setFlyStrategy(flyStrategy())  //通过调用同样被@Bean注解的方法,实现setter方法注入        return a;     }    @Bean    public FlyStrategy flyStrategy(){        return new FlyStrategy();    }  }/* *方法二:自动装配 *该方法无需将待装配的bean声明到同一个配置类中,你可以将待装配的bean的声明分散到其它配置类或xml文件中,甚至可以装配通过自动扫描而创建的bean */@Configurationpublic class BeanConfig{    @Bean    public Animal animal_constructorWiring(FlyStrategy f){        return new Animal(f);     }    @Bean    public Animal animal_setterWiring(FlyStrategy f){        Animal a = new Animal();        a.setFlyStrategy(f))          return a;     } }

2.3、通过XML装配Bean

(1)创建bean

<!-- 最简方式,默认beanId为packageName.Animal#0 --><bean class="packageName.Animal"/>

(2)装配bean

<bean id="flyStrategy" class="packageName.FlyStrategy"/><!-- 将字面量注入到构造器 --><bean class="packageName.Animal"/>    <constructor-arg value="literal"/></bean><!-- 将bean注入到构造器 --><bean class="packageName.Animal"/>    <constructor-arg ref="flyStrategy"/></bean><!-- 将字面量集合注入到构造器 --><bean class="packageName.Animal"/>    <constructor-arg>        <list>            <value>literal_1</value>            <value>literal_2</value>        </list>    </constructor-arg></bean><!-- 将bean集合注入到构造器 --><bean class="packageName.Animal"/>    <constructor-arg>        <list>            <ref bean="flyStrategy_1"/>            <ref bean="flyStrategy_2"/>        </list>    </constructor-arg></bean>

以上都是构造器注入,对于setter注入也是类似的,只不过Spring在进行setter注入时依赖于对象对应属性的setter方法,故我们需确保该方法存在且正确。

2.4、导入和混合配置

在一个项目中以上三种装配方式是可以同时使用的。

(1)在Java配置中引用其它Java配置

@Configuration@Import({BeanConfig.Class})

(2)在Java配置中引用XML配置

@Configuration@ImportResource("classpath:spring.xml")

(3)在XML配置中引用其它XML配置

<import resource="classpath:spring.xml"/>

(4)在XML配置中引用Java配置

<bean class="packageName.BeanConfig"/>

三、Bean的生命周期

是不是感觉在Spring中bean的创建过程和自己没多大关系呢,bean只要声明后Spring就帮你接管了bean创建的活儿了。在一般情况下,丢给Spring让它以默认的过程创建出来的bean已经足够我们使用了,但是在Bean的生命周期里,Spring也提供了诸多扩展点,可以让我们自定义bean的创建过程以及销毁过程。

bean的生命周期有4个关键的阶段:(1)bean的实例化 (2)bean的依赖注入 (3)bean可以使用了 (4)容器被关闭,bean也要被销毁

在以上4个阶段的之前和之后我们都可以通过相应的扩展点,在bean的生命周期中横插一脚。

以下是一个简化的bean生命周期:
这里写图片描述
这里写图片描述
——摘自《Spring实战》

其中第6条和第8条描述有误,不是”如果bean实现了BeanPostProcessor接口”,而是”如果容器中存在实现了BeanPostProcessor接口的bean”。这里的BeanPostProcessor接口与其它接口不同,其它接口是Bean级生命周期接口方法,是需要你的bean去实现的,而BeanPostProcessor是容器级生命周期接口方法,只要该容器中有一个该接口的实现即可(该接口的实现会被容器自动发现),并且它会影响该容器中所有Bean的生命周期。

更加完整的bean生命周期可参考此篇博文:
Spring Bean的生命周期(非常详细)
这里写图片描述

以及官方文档:
Customizing the nature of a bean

四、Bean的容器

BeanFactory接口并不能算是一个容器,它只能算是一个功能,而ApplicationContext接口才是一个Spring中最基本的容器,它继承了BeanFactory接口,表明它具有生产bean的功能,除此之外还继承了许多其它接口,以提供了其它应用框架级别的服务。

Spring中常用的几种类型的应用上下文:

  • ClassPathXmlApplicationContext:从XML中加载Spring应用上下文
  • XmlWebApplicationContext:从XML中加载Spring Web应用上下文
  • AnnotationConfigApplicationContex:从Java配置类中加载Spring应用上下文
  • AnnotationConfigWebApplicationContext:从Java配置类中加载Spring Web应用上下文

参考资料

参考书籍:
《Spring实战》——Craig Walls
第1章 Spring之旅
第2章 装配Bean
第3章 高级装配

《深入分析Java Web技术内幕》——许令波
第13章 Spring框架的设计理念与设计模式分析

参考链接:
Spring Framework Reference Documentation
跟我学spring3
Spring 和SpringMVC 的父子容器关系

原创粉丝点击