
最q研ISpringQ她包含的编E思想让我耳目一新。所以写下这入门文章供新手参考。我不是什么Spring的资qIh员,我只是现学现卖。所以文章也只能是肤单薄,错误隑օQ还误谅?br />一、 Spring诞生
Spring是一个开源框Ӟ目前在开源社区的人气很旺Q被认ؓ是最有前途的开源框架之一。她是由Rod Johnson创徏的,她的诞生是ؓ了简化企业pȝ的开发。说道Spring׃得不说EJBQ因为Spring在某U意义上是EJB的替代品Q她是一U轻量的容器。用qEJB的h都知道EJB很复杂,Z一个简单的功能你不得不~写多个Java文g和部|文Ӟ他是一U重量的容器。也怽不了解EJBQ你可能对“轻Q重Q量U”和“容器”比较陌生,那么q里我简单介l一下?br />1、什么是容器
“容器”,q个概念困扰我好久。从学习Tomcat开始就一直对此感到困惑。感性的来讲Q容器就是可以用来装东西的物品。那么在~程领域是指用来装对象QOO的思想Q如果你qOO都不了解Q徏议你d习OO先)的对象。然而这个对象比较特别,它不仅要容纳其他对象Q还要维护各个对象之间的关系。这么讲可能q是太抽象,来看一个简单的例子Q?br />代码片断1Q?br />
- public class Container
- {
- public void init()
- {
- Speaker s = new Speaker();
- Greeting g = new Greeting(s);
- }
- }
可以看到q里的Containerc(容器Q在初始化的时候会生成一个Speaker对象和一个Greeting对象Qƈ且维持了它们的关p,当系l要用这些对象的时候,直接问容器要可以了。这是容器最基本的功能,l护pȝ中的实例Q对象)。如果到q里你还是感到模p的话,别担心,我后面还会有相关的解释?br />
2、轻量与重量
所谓“重量”是相对于“轻量”来讲的Q也可以说“轻量”是相对于重量来讲的。在Spring出现之前Q企业开发一般都采用EJBQ因为它提供的事务管理,声明式事务支持,持久化,分布计算{等都“简化”了企业U应用的开发。我q里的“简化”打了双引号Q因是相对的。重量容器是一U入侵式的,也就是说你要用EJB提供的功能就必须在你的代码中体现出来你用的是EJBQ比如承一个接口,声明一个成员变量。这样就把你的代码绑定在EJB技术上了,而且EJB需要JBOSSq样的容器支持,所以称之ؓ“重量”?br />相对而言“轻量”就是非入R式的Q用Spring开发的pȝ中的cM需要依赖Spring中的c,不需要容器支持(当然Spring本n是一个容器)Q而且Spring的大和q行开支都很微量。一般来_如果pȝ不需要分布计或者声明式事务支持那么Spring是一个更好的选择?br />
二、 几个核心概?br />在我看来Spring的核心就是两个概念,反向控制QIoCQ,面向切面~程QAOPQ。还有一个相关的概念是POJOQ我也会略带介绍?br />1、POJO
我所看到q的POJO全称有两个,Plain Ordinary Java ObjectQPlain Old Java ObjectQ两个差不多Q意思都是普通的Javac,所以也不用ȝ谁对谁错。POJO可以看做是简单的JavaBeanQ具有一pdGetterQSetterҎ的类Q。严格区分这里面的概忉|有太大意义,了解一下就行?br />2、 IoC
IoC的全U是Inversion of ControlQ中文翻译反向控制或者逆向控制。这里的反向是相对EJB来讲的。EJB使用JNDI来查N要的对象Q是d的,而Spring是把依赖的对象注入给相应的类Q这里涉及到另外一个概念“依赖注入”,E后解释Q,是被动的Q所以称之ؓ“反向”。先看一D代码,q里的区别就很容易理解了?br />代码片段2Q?br />
- public void greet()
- {
- Speaker s = new Speaker();
- s.sayHello();
- }
代码片段3Q?br />
- public void greet()
- {
- Speaker s = (Speaker)context.lookup("ejb/Speaker");
- s.sayHello();
- }
代码片段4Q?br />
- public class Greeting
- {
- public Speaker s;
- public Greeting(Speaker s)
- {
- this.s = s;
- }
- public void greet()
- {
- s.sayHello();
- }
- }
我们可以Ҏ一下这三段代码。其中片D?是不用容器的~码Q片D?是EJB~码Q片D?是Spring~码。结合代码片D?Q你能看出来Spring~码的优之处吗Q也怽会觉得Spring的编码是最复杂的。不q没关系Q我在后面会解释Spring~码的好处?br />q里我想先解释一下“依赖注入”。根据我l的例子可以看出QGreetingcM赖SpeakercR片D?和片D?都是d的去获取SpeakerQ虽然获取的方式不同。但是片D?q没有去获取或者实例化Speakerc,而是在greeting函数中直接用了s。你也许很容易就发现了,在构造函C有一个s被注入(可能你^时用的是Q传入)。在哪里注入的呢Q请回头看一下代码片D?Q这是使用容器的好处,由容器来l护各个cM间的依赖关系Q一般通过Setter来注入依赖,而不是构造函敎ͼ我这里是Z化示例代码)。Greetingq不需要关心Speaker是哪里来的或是从哪里获得SpeakerQ只需要关注自己分内的事情Q也是让Speaker说一句问候的话?br />3、 AOP
AOP全称是Aspect-Oriented ProgrammingQ中文翻译是面向斚w的编E或者面向切面的~程。你应该熟悉面向q程的编E,面向对象的编E,但是面向切面的编E你也许是第一ơ听说。其实这些概念听h很玄Q说到底也就是一句话的事情?br />现在的系l往往减小模块之间的耦合度,AOP技术就是用来帮助实现这一目标的。D例来_假如上文的Greetingpȝ含有日志模块Q安全模块,事务理模块Q那么每一ơgreet的时候,都会有这三个模块参与Q以日志模块ZQ每ơgreet之后Q都要记录下greet的内宏V而对于Speaker或者Greeting对象来说Q它们ƈ不知道自q行ؓ被记录下来了Q它们还是像以前一L工作Qƈ没有M区别。只是容器控制了日志行ؓ。如果这里你有点p涂Q没关系Q等讲到具体Spring配置和实现的时候你明白了?br />假如我们现在为Greetingpȝ加入一个Valediction功能Q那么AOP模式的系l结构如下:
G|RET|TIN|G
V|ALE|DIT|ION
| | |
日志 安全 事务
q些模块是诏I在整个pȝ中的Qؓpȝ的不同的功能提供服务Q可以称每个模块是一个“切面”。其实“切面”是一U抽象,把系l不同部分的公共行ؓ抽取出来形成一个独立的模块Qƈ且在适当的地方(也就是切入点Q后文会解释Q把q些被抽取出来的功能再插入系l的不同部分?br />从某U角度上来讲“切面”是一个非常Ş象的描述Q它好像在系l的功能之上横切一刀Q要惌pȝ的功能l,必dq了q个切面。这些切面监视ƈ拦截pȝ的行为,在某些(被指定的Q行为执行之前或之后执行一些附加的dQ比如记录日志)。而系l的功能程Q比如GreetingQƈ不知道这些切面的存在Q更不依赖于q些切面Q这样就降低了系l模块之间的耦合度?br />
三、 Spring初体?br />q一节我用一个具体的例子GreetingQ来说明使用Spring开发的一般流E和ҎQ以及Spring配置文g的写法?br />首先创徏一个Speakerc,你可以把q个cȝ做是POJO?br />代码片段5Q?br />
- public class Speaker
- {
- public void sayHello()
- {
- System.out.println("Hello!");
- }
- }
- public class Greeting
- {
- private Speaker speaker;
- public void setSpeaker(Speaker speaker)
- {
- this.speaker = speaker;
- }
- public void greet()
- {
- speaker.sayHello();
- }
- }
然后要创Z个Spring的配|文件把q两个类兌h?br />代码片段7QapplicationContext.xmlQ:
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
- "http://www.springframework.org/dtd/spring-beans.dtd">
- <beans>
- <bean id="Speaker" class="Speaker"></bean>
- <bean id="Greeting" class="Greeting">
- <property name="speaker">
- <ref bean="Speaker"/>
- </property>
- </bean>
- </beans>
要用Spring Framework必须把Spring的包加入到Classpath中,我用的是Eclipse+MyEclipseQ这些工作是自动完成的。推荐用Spring的配|文件编辑器来编辑,U手工编写很Ҏ出错。我先分析一下这个xml文g的结构,然后再做试。从<beans>节点开始,先声明了两个<bean>Q第二个bean有一个speaker属性(propertyQ要求被注入Q注入的内容是另外一个bean Speaker。这里的命名是符合JavaBean规范的,也就是说如果是speaker属性,那么Spring容器׃调用setSpeaker()来注入这个属性?lt;ref>是reference的意思,表示引用另外一个bean?br />下面看一D늮单的试代码Q?br />代码片段8Q?br />
- public static void main(String[] args)
- {
- ApplicationContext context =
- New ClassPathXmlApplicationContext("applicationContext.xml");
- Greeting greeting = (Greeting)context.getBean("Greeting");
- greeting.greet();
- }
q段代码很简单,如果你上文都看懂了,那么q里应该没有问题。值得注意的是Spring有两U方式来创徏容器Q我们不再用上文我们自己~写的ContainerQ,一U是ApplicationContextQ另外一U是BeanFactory。ApplicationContext更强大一些,而且使用上两者没有太大区别,所以一般说来都用ApplicationContext。Spring容器帮助我们l护我们在配|文件中声明的Bean以及它们之间的依赖关p,我们的Bean只需要关注自q核心业务?br />
四、 面向接口的~程
看了q么多,也许你ƈ没有觉得Springl开发带来了很多便利。那是因为我丄例子q不能突出Spring的优之处,接下来我通过接口~程来体现Spring的强大?br />假如现在要求扩展Greeting的功能,要让Speaker用不同的语言来问候,也就是说有不同的SpeakerQ比如ChineseSpeaker, EnglishSpeaker。那么对上文提到的三U编码方式(代码片段2??Q分别加以修改,你会发现很麻烦。假如下ơ又要加入一个西班牙语,又得重复力_。很自然的会考虑C用一个ISpeaker接口来简化工?Q更改后的代码如下(q里没有列出接口的相关代码,我想你应该明白怎么写)Q ?br />
代码片段9Q?br />
- public void greet()
- {
- ISpeaker s = new ChineseSpeaker();
- s.sayHello();
- }
代码片段10Q?br />
- public void greet()
- {
- ISpeaker s = (ISpeaker)context.lookup("ejb/ChineseSpeaker");
- s.sayHello();
- }
代码片段11Q?br />
- public class Greeting
- {
- public ISpeaker s;
- public Greet(ISpeaker s)
- {
- this.s = s;
- }
- public void greet()
- {
- s.sayHello();
- }
- }
关于Spring的接口编E还有很多东西可以去挖掘Q后文还会提到有关Spring Proxy的接口编E,我这里先介绍q么多,有兴话可以去google更多的资料?br />
五、 应用Spring中的切面
Spring生来支持AOPQ首先来看几个概念:
1、 切面(AspectQ:切面是系l中抽象出来的的某一个功能模块,上文已经有过介绍Q这里不再多说?br />2、 通知QAdviceQ:通知是切面的具体实现。也是说你的切面要完成什么功能,具体怎么做就是在通知里面完成的。这个名UC乎有点让解,{后面看了代码就明白了?br />3、 切入点QPointcutQ:切入点定义了通知应该应用到系l的哪些地方。Spring只能控制到方法(有的AOP框架可以控制到属性)Q也是说你能在Ҏ调用之前或者之后选择切入Q执行额外的操作?br />4、 目标对象(TargetQ:目标对象是被通知的对象。它可以是Q何类Q包括你自己~写的或者第三方cR有了AOP以后Q目标对象就只需要关注自q核心业务Q其他的功能Q比如日志,qAOP框架支持完成?br />5、 代理(ProxyQ:单的Ԍ代理是通知应用到目标对象后产生的对象。Spring在运行时会给每个目标对象生成一个代理对象,以后所有对目标对象的操作都会通过代理对象来完成。只有这样通知才可能切入目标对象。对pȝ的其他部分来_q个q程是透明的,也就是看h跟没用代理一栗?br />我ؓ了简化,只介l这5个概c通过q几个概念应该能够理解Spring的切面编E了。如果需要深入了解Spring AOP的话再去学习其他概念也很快的?br />下面通过一个实际的例子来说明Spring的切面编E。l上文Greeting的例子,我们惛_Speaker每次说话之前记录Speaker被调用了?br />首先创徏一个LogAdvicec:
代码片段12Q?br />
- <beans>
- <bean id="Speaker" class="Speaker"/>
- <bean id="Greeting" class="Greeting">
- <property name="speaker">
- <ref bean="SpeakerProxy"/>
- </property>
- </bean>
- <bean id="LogAdvice" class="LogAdvice"/>
- <bean id="SpeakerProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
- <property name="proxyInterfaces">
- <value>ISpeaker</value>
- </property>
- <property name="interceptorNames">
- <list>
- <value>LogAdvice</value>
- </list>
- </property>
- <property name="target">
- <ref local="Speaker"/>
- </property>
- </bean>
- </beans>
可以看到我们的配|文件中多了两个beanQ一个LogAdviceQ另外一个SpeakerProxy。LogAdvice很简单。我着重分析一下SpeakerProxy。这个Bean实际上是由Spring提供的ProxyFactoryBean实现。下面定义了三个依赖注入的属性?br />1、 proxyInterfactesQ这个属性定义了q个Proxy要实现哪些接口,可以是一个,也可以是多个Q多个的话,要用list标签Q。我前面讲过Proxy是在q行是动态创建的Q那么这个属性就告诉Spring创徏q个Proxy的时候实现哪些接口?br />2、 interceptorNamesQ这个属性定义了Proxy被切入了哪些通知Q这里只有一个LogAdvice?br />3、 targetQ这个属性定义了被代理的对象。在q个例子中target是Speaker?br />q样的定义实际上U束了被代理的对象必dC个接口,q与上文讲的面向接口的编E有点类伹{其实可以这L解,接口的定义可以让pȝ的其他部分不受媄响,以前用ISpeaker接口来调用,现在加入了Proxyq是一L。但实际上内容已l不一样了Q以前是SpeakerQ现在是一个Proxy。而target属性让proxy知道具体的方法实现在哪里。Proxy可以看作是target的一个包装。当然Springq没有强制要求用接口Q通过CGLIBQ一个高效的代码生成开源类库)也可以直接根据目标对象生成子c,但这U方式ƈ不推荐?br />我们q像以前一L试我们的GreetingpȝQ测试代码和代码片段8是一L。运行结果如下:
Speaker called!
Hello!
看到效果了吧Q而且你可以发玎ͼ我们加入Log功能q没有改变以前的代码Q甚x试代码都没有改变Q这是AOP的魅力所在!我们更改的只是配|文件?br />下面解释一下刚才落下的MethodBeforeAdvice。关于这个类我ƈ不详l介l,因ؓq涉及到Spring中的另外一个概念“连接点QJointpointQ”,我详l介l一个beforeq个Ҏ。这个方法有三个参数arg0表示目标对象在哪个点被切入了Q既然是MethodBeforeAdviceQ那当然是在Method之前被切入了。那么arg0是表示的那个Method。第二个参数arg1是Method的参敎ͼ所以类型是Object[]。第三个参数是目标对象了,在Greeting例子中arg2的类型实际上是Speaker?br />在Greeting例子中,我们q没有指定目标对象的哪些Ҏ要被切入Q而是默认切入所有方法调用(虽然Speaker只有一个方法)。通过自定义PointcutQ可以控制切入点Q我q里不再介绍了,因ؓqƈ不媄响理解Spring AOPQ有兴趣的话去google一下就知道了?br />
六、实战Spring
虽然q部分取名ؓ“实战Spring”,但实际上我ƈ不打在q里介绍实际开发Spring的内容,因ؓ我写q篇文章的目的是介绍Spring的概念和用Spring开发的思\Q而不是有关Spring的实践和详细介绍。文中介l的内容和用Spring做实际开发还相去甚远。之所以取名“实战Spring”是我觉得理解了上文讲的内容以后Q可以开始ؓ深入学习Spring和学习如何在目中应用Spring了?br />要系l的学习Springq是需要阅M本详l介lSpring的书Q我推荐Spring in ActionQ因为我看的是q本书。希望这文章能Zpȝ的学习Spring扫除一些障?br />