http://blog.blogchina.com/category.307634.html
1,控制反转(IoC = Inversion of Control) & 依赖注入(DI = Dependency Injection)
IoC:由容器控制程序之间的关系,而非传统现实中,由程序代码直接控制。控制权由应用代码中转到了外部容器,控制权的转移,即所谓反转。
DI:即组件之间的依赖关系由容器在运行期决定,由容器动态的将某种依赖关系注入到组件之中。
2, 依赖注入的几种实现类型
1)接口注入:
我们常常借助接口来将调用与实现者分离:
public class ClassA {
private InterfaceB clzB;
public doSomething() {
Ojbect obj = Class.forName(Config.BImplementation).newInstance();
clzB = (InterfaceB)obj;
clzB.doIt()
}
......
}
在上面的代码中,ClassA依赖于InterfaceB的实现,如何获得InterfaceB实现类的实例?传统的方法是在代码中创建InterfaceB实现类的实例,并将其赋予ClzB。
而这样一来,ClassA在编译期即依赖于InterfaceB的实现。为了将调用者与实现者在编译期分离,于是有了上面的代码,我们根据预先在配置文件中设定的实现类的类名(Config.BImplementation),动态加载实现类,并通过InterfaceB强制转型后为ClassA所用。这就是接口注入的一个最原始的雏形。
而对于一个接口注入型IoC容器而言,加载接口实现并创建其实例的工作由容器完成。如:
public class ClassA {
private InterfaceB clzB;
public Object doSomething(InterfaceB b) {
clzB = b;
return clzB.doIt();
}
......
}
在运行期,InterfaceB实例将由容器提供。
2)设值注入
即通过类的setter方法完成依赖关系的设置。
3)构造子注入
构造子注入,即通过构造函数完成依赖关系的设定,如:
public class DIByConstructor {
private final DataSource dataSource;
private final String message;
public DIByConstructor(DataSource ds, String msg) {
this.dataSource = ds;
this.message = msg;
}
......
}
在构造子注入的依赖注入机制中,依赖关系是通过类构造函数建立,容器通过调用类的构造方法,将其所需的依赖关系注入其中。
4)几种依赖注入模式的对比总结
接口注入模式:由于其历史悠久,在很多容器中都已经得到了应用。但由于其在灵活性,易用性上不如其它两种注入模式,因为在IOC的专题世界内并不被看好。
设值注入的优势:第一,对于习惯了传统JavaBean开发的程序员而言,通过setter方法设定依赖关系显得更加直观,更加自然;第二,如果依赖关系(或继承关系)较为复杂,那么构造子注入模式的构造函数也会相当庞大(我们需要在构造函数中设定所有的依赖关系),此时设值注入模式往往更为简洁;第三,对于某些第三方类库而演,可能要求我们的组件必须提供一个默认的构造函数,此时构造子注入模式的依赖注入机制就体现出其局限性,难以完成我们期望的功能。
构造子注入的优势:第一,"在构造期即创建一个完整合法的对象",对于这条java设计原则,构造子注入无疑是最好的响应者;第二,避免了繁琐的setter方法的编写,所有依赖关系均在构造函数中设定,依赖关系集中呈现,更加易读;第三,由于没有setter方法,所有依赖关系均在构造时由容器一次性设定,因此组件在被创建之后即处于相对"不变"的稳定状态,无需担心上层代码在调用过程中就执行setter方法对组件依赖关系产生破坏,特别是对于Singleton模式的组件而言,这可能对整个系统产生重大的影响;第四,同样,对于关联关系仅在构造函数中表达,只有组件创建者需要关心组件内部的依赖关系。对于调用者而言,组件中的依赖关系处于黑盒之中。对上层屏蔽不必要的信息,也为系统的层次清晰性提供了保证;第五,通过构造子注入,意味着我们可以在构造函数中决定依赖关系的注入顺序,对于一个大量依赖外部服务的组件而言,依赖关系的获得顺序可能非常重要,比喻某个依赖关系注入的先决条件是组件的DataSource及相关资源已经被设定。
3,Spring Bean封装机制
Spring从核心而言,是一个DI容器,其设计哲学是提供一个无侵入式的高扩展性框架。即无需代码中涉及Spring专有类,即可将其纳入Spring容器进行管理。
作为对比,EJB则是一个高度侵入性的框架规范,它制定了众多的接口和编码规范,要求实现者必须遵从。侵入性的后果就是,一旦系统基于侵入性框架设计开发,那么之后任何脱离这个框架的企图都将付出极大的代价。
为了避免这种情况,实现无侵入性的目标。Spring大量引入了java的Reflection机制,通过动态调用的方式避免硬编码方式的约束,并在此基础上建立了其核心组件BeanFactory,以此作为其依赖注入机制的实现基础。
org.springframework.beans包中包括了这些核心组件的实现类,核心中的核心为BeanWrapper和BeanFactory类。这两个类从技术角度而言并不复杂,但对于Spring框架而言,却是关键所在。
4,Bean Wrapper
所谓依赖注入,即在运行期由容器将依赖关系注入到组件之中。讲得通俗点,就是在运行期,由Spring根据配置文件,将其他对象的引用通过组件提供的setter方法进行设定。
我们知道,如果动态设置一个对象属性,可以借助Java的Reflection机制完成:
Class cls = Class.forName("net.xiaxin.beans.User");
Method mtd = cls.getMethod("setName",new Class[]{String.class});
Object obj = (Object)cls.newInstance();
mtd.invoke(obj,new Object[]{"Erica"});
return obj;
上面我们通过动态加载了User类,并通过Reflection调用了User.setName方法设置其name属性。对于这里的例子而言,处于简洁,我们将类名和方法名都以常量的方法硬编码。假设这些常量都是通过配置文件读入,那我们就实现了一个最简单的BeanWrapper。这个BeanWrapper的功能很简单,提供一个设置JavaBean属性的通用方法(Apache BeanUtils类库中提供了大量针对Bean的辅助工具,如果有兴趣可以下载一份源代码加以研读)。
Spring BeanWrapper基于同样的原理,提供了一个更加完善的实现。看看如何通过Spring BeanWrapper操作一个JavaBean:
Object obj = Class.forName("net.xiaxin.beans.User").newInstance();
BeanWrapper bw = new BeanWrapperImpl(obj);
bw.setPropertyValue("name", "Erica");
System.out.println("User name=>"+bw.getPropertyValue("name"));
对比之前的代码,相信大家已经知道BeanWrapper的实现原理。
诚然,通过这样的方式设定JavaBean属性实在繁琐,但它却提供了一个通用的属性设定机制,而这样的机制,也正是Spring依赖注入机制所依赖的基础。
通过BeanWrapper,我们可以无需在编码时就指定JavaBean的实现类和属性值,通过在配置文件加以设定,就可以在运行期动态创建对象并设定其属性(依赖关系)。
上面的代码中,我们仅仅指定了需要设定的属性名"name",运行时,BeanWrapper将根据JavaBean规范,动态调用对象的"setName"方法进行属性设定。属性名可以包含层次,如对于属性名"address.zipcode",BeanWrapper会调用"getAddress().setZipcode"方法。
5,Bean Factory
顾名思义,负责创建并维护Bean实例。
Bean Factory负责根据配置文件创建Bean实例,可以配置的项目有:
1)Bean属性值以及依赖关系(对其他Bean的引用)
2)Bean创建模式(是否Singleton模式,即是否只针对指定类维持全局唯一的实例)
3)Bean初始化和销毁方法
4)Bean的依赖关系
下面是一个较为完整的Bean配置示例:
Spring Bean Configuration Sample
id="TheAction" ⑴
class="net.xiaxin.spring.qs.UpperAction" ⑵
singleton="true" ⑶
init-method="init" ⑷
destroy-method="cleanup" ⑸
depends-on="ActionManager" ⑹
>
HeLLo ⑺
⑻
java:comp/env/jdbc/sample
(1)id:Java Bean在BeanFactory中的唯一标识,代码中通过BeanFactory获取JavaBean实例时需以此作为索引名称。
(2)class:Java Bean类名。
(3)singleton:指定此Java Bean是否采用单例(singleton)模式,如果设置为"true",则在BeanFactory作用范围内,只维护此Java Bean的一个实例,代码通过BeanFactory获得此Java Bean实例的引用。反之,如果设为"false",则通过BeanFactory获取此Java Bean实例时,BeanFactory每次都将创建一个新的实例返回。
(4)init-method:初始化方法,此方法将在BeanFactory创建JavaBean实例之后,在向应用层返回引用之前执行。一般用语一些资源的初始化工作。
(5)destroy-method:销毁方法。此方法将在BeanFactory销毁的时候执行,一般用于资源释放。
(6)depends-on:Bean依赖关系。一般情况下无需设定。Spring会根据情况组织各个依赖关系的构建工作(这里示例中的depends-on属性非必须)。只有在某些特殊情况下,如JavaBean中的某些静态变量需要进行初始化(这是一种BadSmell,应该在设计上避免)。通过depends-on指定其依赖关系可以保证在此Bean加载之前,首先对depends-on所指定的资源进行加载。
(7):通过节点可指定属性值。BeanFactory将自动根据JavaBean对应的属性类型加以匹配。上面的"desc"属性提供了一个null值的设定示例,需要注意的是代表一个空字符串,如果需要将属性值设定为null,必须使用节点。
(8):指定了属性对BeanFactory中其他Bean的引用关系。示例中,TheAction的dataSource属性引用了id为dataSource的Bean。BeanFactory将在运行期创建dataSource bean实例,并将其引用传入TheAction Bean的dataSource属性中。
下面的代码演示了如何通过BeanFactory获取Bean实例:
InputStream is = new FileInputStream("bean.xml");
XmlBeanFactory factory = new XmlBeanFactory(is);
Action action = (Action) factory.getBean("TheAction");
此时我们获得的Action实例,由BeanFactory进行加载,并根据配置文件进行了初始化和属性设定。
联合上面关于BeanWrapper的内容,我们可以看到,BeanWrapper实现了针对单个Bean的属性设定操作。而BeanFactory则是针对多个Bean的管理容器,根据给定的配置文件,BeanFactory从中读取类名,属性名/值,然后通过Reflection机制进行Bean加载和属性指定。
6,ApplicationContext
BeanFactory提供了针对JavaBean的管理功能,而ApplicationContext提供了一个更为框架化的实现(从上面的示例中可以看出,BeanFactory的使用方式更加类似一个API,而非Framework style)。
ApplicationContext覆盖了BeanFactory的所有功能,提供了更为开放式的实现(如对于Web应用,我们可以在web.xml中对ApplicationContext进行配置)。
相对于BeanFactory而言,ApplicationContext提供了以下扩展功能:
1)国际化支持:
我们可以在Beans.xml文件中,对程序中的语言信息(如提示信息)进行定义,将程序中的提示信息抽取到配置文件中加以定义,为我们进行应用的各语言版本转换提供了极大的灵活性。
2)资源访问
支持对文件和URL的访问
3)事件传播
事件传播特性为系统中状态改变时的检测提供了良好的支持。
4)多实例加载
可以在同一个应用中加载多个Context实例。
下面分别对这些特性进行介绍:
1)国际化支持
国际化支持在实际开发中可能是最常用的特性。对于一个需要支持不同语言环境的应用而言。我们所采取的最常用的策略一般是通过一个独立的资源文件(如一个properties文件)完成所有语言信息(如界面上的提示信息)的配置。Spring对这种传统的方式进行了封装,并提供了更加强大的功能,如信息的自动装配以及热部署功能(配置文件修改后自动读取,而无需重新启动程序),下面是一个典型的示例:
Spring Quick Start
class="org.springframework.context.support.ResourceBundleMessageSource">
messages
这里声明了一个名为messageSource的Bean(注意对于Message定义,Bean ID必须为messageSource,这是目前Spring的编码规约),对应类为ResourceBundleMessageSource,目前Spring中提供了两个MessageSource接口的实现,即ResourceBundleMessageSource和ReloadableResourceBundleMessageSource,后者提供了无需重启即可重新加载配置信息的特性。
在配置节点中,我们指定了一个配置名"messages"。Spring会自动在CLASSPATH根路径中按照如下顺序搜寻配置文件并进行加载(以Locale为zh_CH为例):
messages_zh_CN.properties
messages_zh.properties
messages.properties
messages_zh_CN.class
messages_zh.class
messages.class
(Spring实际上调用了JDK的ResourceBundle读取配置文件)
示例中包含了两个配置文件,内容如下:
messages_zh_CN.properties:
userinfo=当前登陆用户:[{0}] 登陆时间:[{1}]
messages_en_US.properties:
userinfo=Current Login user:[{0}] Login time:[{1}]
我们可以通过下面的语句进行测试:
ApplicationContext ctx=new
FileSystemXmlApplicationContext("bean.xml");
Object[] arg = new Object[]{"Erica", Calendar.getInstance().getTime()};
//以系统默认Locale加载信息(对于中文WinXP而言,默认为zh_CN)
String msg = ctx.getMessage("userinfo", arg);
System.out.println("Message is ===> "+msg);
代码中,我们将一个Object数组arg作为参数传递给ApplicationContext.getMessage方法,这个参数中包含了出现在最终文字信息中的可变内容,ApplicationContext将根据参数中的Locale信息对其进行处理(如针对不同Locale设定日期输出格式),并用其替换配置文件中的{n}标识(n代表参数数组中的索引,从1开始)。
根据当前默认Locale"zh_CH",getMessage方法自动加载了message_zh_CH.properties文件。JVM会根据当前系统的Locale设定进行相应处理。可以通过在JVM启动参数中追加"-Duser.language=en"来设定当前JVM语言类型,通过JVM级的设定,结合国际化支持功能,我们可以较为简单的实现多国语言系统的自动部署切换。
getMessage还有一个中指定Locale参数的版本,直接指定加载对应的properties文件:
String msg = ctx.getMessage("userinfo", arg, Locale.US);
2)资源访问
ApplicationContext.getResource方法提供了对资源文件访问支持,如:
Resource rs = ctx.getResource("classpath:config.properties");
File file = rs.getFile();
上例从CLASSPATH根路径中查找config.properties文件并获取其文件句柄。getResource方法的参数为一个资源访问地址,如:
file:C:/config.properties
/config.properties
classpath:config.properties
注意getResource返回的Resource并不一定实际存在,可以通过Resource.exists()方法对其进行判断。
3)事件传播
ApplicationContext基于Observer模式(java.util包中有对应实现),提供了针对Bean的事件传播功能。通过Application.publishEvent方法,我们可以将事件通知系统内所有的ApplicationListener。
事件传播的一个典型应用是,当Bean中的操作发生异常(如数据库连接失败),则通过事件传播机制通知异常监听器进行处理。
在目前版本的Spring中,事件传播部分的设计还有待改进。同时,如果能够进一步支持异步事件处理机制,无疑更具吸引力。
org.springframework.context.event.ApplicationEventMulticasterImpl实现了事件传播机制,目前还相当简陋。
在运行期,ApplicationContext会自动在当前的所有Bean中寻找ApplicationListener接口的实现,并将其作为事件接收对象。当Application.publishEvent方法调用时,所有的ApplicationListener接口实现都会被激发,每个ApplicationListener可根据事件的类型判断是否是自己需要处理的事件。
7,Web Context
对于Web应用,Spring提供了可配置的ApplicationContext加载机制。
加载器目前有两种选择:ContextLoaderListener和ContextLoaderServlet。这两者在功能上完全等同,只是一个是基于Servlet2.3版本中新引入的Listener接口实现,而另一个基于Servlet接口实现,开发中可根据目标Web容器的实际情况进行选择。
配置非常简单,在web.xml中增加:
org.springframework.web.context.ContextLoaderListener
或:
context
org.springframework.web.context.ContextLoaderServlet
1
通过以上配置,Web容器会自动加载/WEB-INF/applicationContext.xml初始化ApplicationContext实例,如果需要指定配置文件位置,可通过context-param加以指定:
contextConfigLocation
/WEB-INF/myApplicationContext.xml
配置完成之后,即可通过:WebApplicationContextUtils.getWebApplicationContext方法在Web应用中获取ApplicationContext引用。
8,Aspect Oriented Programming
OOP(面向对象编程)针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。
AOP(面向切面编程)针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤和阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。
AOP还有另外一个重要特点:源码组成无关性。倘若应用中通过某个具体的业务逻辑类实现了独立的权限检查,而请求调度方法通过预编码调用这个权限模块实现权限管理,那么这也不算是AOP。对于AOP组件而言,很重要的一点就是源码组成无关性,所谓源码组成无关性,体现在具体设计中就是AOP组件必须与应用代码无关,简单来讲,就是应用代码可以脱离AOP组件独立编译。
为了实现源码组成无关性,AOP往往通过预编译方式(如AspectJ)和运行期动态代理模式(如Spring AOP和JBoss AOP)实现。
下面先来看AOP中几个比较重要的概念:
1)切面(Aspect)
通过切面,我们可以将系统中各个不同层次上的问题隔离开来,实现统一集约式处理。各个切面只需要集中于自己领域内的逻辑实现。这一方面使得开发逻辑更加清晰,专业化分工更加易于进行;另一方面,由于切面的隔离,降低了偶合性,我们就可以在不同的应用中将各个切面组合使用,从而使得代码可重用性大大增强。
2)连接点(JoinPoint)
程序运行过程中的某个阶段点。如某个方法调用,或者某个异常被抛出。
3)处理逻辑(Advice)
在某个连接点所采用的处理逻辑。处理逻辑调用模式通常有三种:
第一:Around,在连接点前后插入预处理过程和后处理过程;
第二:Before,仅在连接点之前插入预处理过程;
第三:Throw,在连接点抛出异常时进行异常处理。
4)切点(PointCut)
一系列连接点的集合,它指明处理方式(Advice)将在何时被触发。
9,AOP in Spring
Spring中提供的内置AOP支持,是基于动态AOP机制实现。从技术角度来讲,所谓动态AOP,即通过动态Proxy模式,在目标对象的方法调用前后插入相应的处理代码。Spring AOP中的动态Proxy模式,则是基于Java Dynamic Proxy(面向Interface)和CGLIB(面向Class)实现。