﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-sparkler-文章分类-Spring</title><link>http://www.blogjava.net/sparkler/category/16771.html</link><description>海上生明月,天涯共此时</description><language>zh-cn</language><lastBuildDate>Tue, 27 Feb 2007 08:49:39 GMT</lastBuildDate><pubDate>Tue, 27 Feb 2007 08:49:39 GMT</pubDate><ttl>60</ttl><item><title>理解spring (转自appFuse in China)</title><link>http://www.blogjava.net/sparkler/articles/78447.html</link><dc:creator>sparkler</dc:creator><author>sparkler</author><pubDate>Wed, 01 Nov 2006 03:54:00 GMT</pubDate><guid>http://www.blogjava.net/sparkler/articles/78447.html</guid><wfw:comment>http://www.blogjava.net/sparkler/comments/78447.html</wfw:comment><comments>http://www.blogjava.net/sparkler/articles/78447.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/sparkler/comments/commentRss/78447.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/sparkler/services/trackbacks/78447.html</trackback:ping><description><![CDATA[
		<style type="text/css"><![CDATA[
body{ font-size:14px}
td{ font-size:14px; line-height:150%}
.STYLE2 {color: #FF0000}
]]&gt;</style>
		<table id="table1" cellspacing="1" cellpadding="3" width="90%" align="center" border="0">
				<tbody>
						<tr>
								<td>    最近研究Spring，她包含的编程思想让我耳目一新。所以写下这篇入门级文章供新手参考。我不是什么Spring的资深研究人员，我只是现学现卖。所以文章也只能是肤浅单薄，错误难免，还请见谅。<br /><br />一、Spring诞生<br />    Spring是一个开源框架，目前在开源社区的人气很旺，被认为是最有前途的开源框架之一。她是由Rod Johnson创建的，她的诞生是为了简化企业级系统的开发。说道Spring就不得不说EJB，因为Spring在某种意义上是EJB的替代品，她是一种轻量级的容器。用过EJB的人都知道EJB很复杂，为了一个简单的功能你不得不编写多个Java文件和部署文件，他是一种重量级的容器。也许你不了解EJB，你可能对“轻（重）量级”和“容器”比较陌生，那么这里我简单介绍一下。<br />    1、什么是容器<br />     “容器”，这个概念困扰我好久。从学习Tomcat开始就一直对此感到困惑。感性的来讲，容器就是可以用来装东西的物品。 那么在编程领域就是指用来装对象（OO的思想，如果你连OO都不了解，建议你去学习OO先）的对象。 然而这个对象比较特别，它不仅要容纳其他对象，还要维护各个对象之间的关系。 这么讲可能还是太抽象，来看一个简单的例子：<br />代码片断1：<br />public class Container{ <br />  public void init(){<br />    Speaker s = new Speaker(); <br />    Greeting g = new Greeting(s); <br />  } <br />} <br />    可以看到这里的Container类（容器）在初始化的时候会生成一个Speaker对象和一个Greeting对象，并且维持了它们的关系，当系统要用这些对象的时候，直接问容器要就可以了。这就是容器最基本的功能，维护系统中的实例（对象）。如果到这里你还是感到模糊的话，别担心，我后面还会有相关的解释。<br />    2、轻量级与重量级<br />    所谓“重量级”是相对于“轻量级”来讲的，也可以说“轻量级”是相对于重量级来讲的。在Spring出现之前，企业级开发一般都采用EJB，因为它提供的事务管理，声明式事务支持，持久化，分布计算等等都“简化”了企业级应用的开发。 我这里的“简化”打了双引号，因为这是相对的。重量级容器是一种入侵式的，也就是说你要用EJB提供的功能就必须在 你的代码中体现出来你使用的是EJB，比如继承一个接口，声明一个成员变量。这样就把你的代码绑定在EJB技术上了，而且EJB需要JBOSS这样的容器支持，所以称之为“重量级”。<br />    相对而言“轻量级”就是非入侵式的，用Spring开发的系统中的类不需要依赖Spring中的类，不需要容器支持（当然Spring本身是一个容器），而且Spring的大小和运行开支都很微量。一般来说，如果系统不需要分布计算或者声明式事务支持那么Spring是一个更好的选择。<br /><br />二、几个核心概念<br />    在我看来Spring的核心就是两个概念，反向控制（IoC），面向切面编程（AOP）。还有一个相关的概念是POJO，我也会略带介绍。<br />    1、POJO<br />    我所看到过的POJO全称有两个，Plain Ordinary Java Object，Plain Old Java Object，两个差不多，意思都是普通的Java类， 所以也不用去管谁对谁错。POJO可以看做是简单的JavaBean（具有一系列Getter，Setter方法的类）。 严格区分这里面的概念没有太大意义，了解一下就行。<br />    2、IoC<br />    IoC的全称是Inversion of Control，中文翻译反向控制或者逆向控制。这里的反向是相对EJB来讲的。 EJB使用JNDI来查找需要的对象，是主动的，而Spring是把依赖的对象注入给相应的类（这里涉及到另外一个概念“依赖注入”， 稍后解释），是被动的，所以称之为“反向”。先看一段代码，这里的区别就很容易理解了。<br />代码片段2：<br />public void greet() { <br />  Speaker s = new Speaker(); <br />  s.sayHello(); <br />} <br />代码片段3：<br />public void greet() { <br />  Speaker s = (Speaker)context.lookup("ejb/Speaker"); <br />  s.sayHello(); <br />} <br />代码片段4：<br />public class Greeting?{ <br />  public Speaker s; <br />  public Greeting(Speaker s){ <br />    this.s = s; <br />  }<br />  public void greet(){ <br />    s.sayHello(); <br />  }<br />} <br />    我们可以对比一下这三段代码。其中片段2是不用容器的编码，片段3是EJB编码，片段4是Spring编码。结合代码片段1，你能看出来Spring编码的优越之处吗？也许你会觉得Spring的编码是最复杂的。不过没关系，我在后面会解释Spring编码的好处。<br />    这里我想先解释一下“依赖注入”。根据我给的例子可以看出，Greeting类依赖Speaker类。片段2和片段3都是主动的去获取Speaker，虽然获取的方式不同。但是片段4并没有去获取或者实例化Speaker类，而是在greeting函数中直接使用了s。你也许很容易就发现了，在构造函数中有一个s被注入（可能你平时用的是，传入）。在哪里注入的呢？请回头看一下代码片段1，这就是使用容器的好处，由容器来维护各个类之间的依赖关系（一般通过Setter来注入依赖，而不是构造函数，我这里是为了简化示例代码）。 Greeting并不需要关心Speaker是哪里来的或是从哪里获得Speaker，只需要关注自己分内的事情，也就是让Speaker说一句问候的话。<br />    3、AOP<br />    AOP全称是Aspect-Oriented Programming，中文翻译是面向方面的编程或者面向切面的编程。 你应该熟悉面向过程的编程，面向对象的编程，但是面向切面的编程你也许是第一次听说。其实这些概念听起来很玄，说到底也就是一句话的事情。<br />    现在的系统往往强调减小模块之间的耦合度，AOP技术就是用来帮助实现这一目标的。举例来说，假如上文的Greeting系统含有日志模块， 安全模块，事务管理模块，那么每一次greet的时候，都会有这三个模块参与，以日志模块为例，每次greet之后，都要记录下greet的内容。 而对于Speaker或者Greeting对象来说，它们并不知道自己的行为被记录下来了，它们还是像以前一样的工作，并没有任何区别。 只是容器控制了日志行为。如果这里你有点糊涂，没关系，等讲到具体Spring配置和实现的时候你就明白了。 <br />    这些模块是贯穿在整个系统中的，为系统的不同的功能提供服务，可以称每个模块是一个“切面”。其实“切面”是一种抽象， 把系统不同部分的公共行为抽取出来形成一个独立的模块，并且在适当的地方（也就是切入点，后文会解释）把这些被抽取出来的功能 再插入系统的不同部分。<br />    从某种角度上来讲“切面”是一个非常形象的描述，它好像在系统的功能之上横切一刀，要想让系统的功能继续，就必须先过了这个切面。 这些切面监视并拦截系统的行为，在某些（被指定的）行为执行之前或之后执行一些附加的任务（比如记录日志）。 而系统的功能流程（比如Greeting）并不知道这些切面的存在，更不依赖于这些切面，这样就降低了系统模块之间的耦合度。<br /><br />三、Spring初体验<br />    这一节我用一个具体的例子Greeting，来说明使用Spring开发的一般流程和方法，以及Spring配置文件的写法。<br />首先创建一个Speaker类，你可以把这个类看做是POJO。<br />代码片段5：<br />public class Speaker{ <br />  public void sayHello(){ <br />    System.out.println( "Hello!"); <br />  } <br />} <br />再创建一个Greeting类。<br />代码片段6：<br />public class Greeting{ <br />  private Speaker speaker; <br />  public void setSpeaker(Speaker speaker){ <br />    this.speaker = speaker; <br />  } <br />  public void greet(){ <br />    speaker.sayHello(); <br />  } <br />} <br />然后要创建一个Spring的配置文件把这两个类关联起来。<br />代码片段7（applicationContext.xml）：<br />&lt;?xml version="1.0" encoding="UTF-8"?&gt; <br />&lt;!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"&gt; <br />&lt;beans&gt; <br />  &lt;bean id="Speaker" class="Speaker"&gt;&lt;/bean&gt; <br />  &lt;bean id="Greeting" class="Greeting"&gt; <br />    &lt;property name="speaker"&gt;&lt;ref bean="Speaker"/&gt;&lt;/property&gt; <br />  &lt;/bean&gt;<br />&lt;/beans&gt; <br />    要用Spring Framework必须把Spring的包加入到Classpath中，我用的是Eclipse+MyEclipse，这些工作是自动完成的。推荐用Spring的配置文件编辑器来编辑，纯手工编写很容易出错。我先分析一下这个xml文件的结构，然后再做测试。从&lt;beans&gt;节点开始，先声明了两个&lt;bean&gt;，第二个bean有一个speaker属性（property）要求被注入，注入的内容是另外一个bean Speaker。这里的命名是符合JavaBean规范的，也就是说如果是speaker属性，那么Spring容器就会调用setSpeaker()来注入这个属性。&lt;ref&gt;是reference的意思，表示引用另外一个bean。<br />下面看一段简单的测试代码：<br />代码片段8：<br />public static void main(String[] args){ <br />  ApplicationContext context = New ClassPathXmlApplicationContext("applicationContext.xml"); <br />  Greeting greeting = (Greeting)context.getBean( "Greeting"); <br />  greeting.greet(); <br />} <br />    这段代码很简单，如果你上文都看懂了，那么这里应该没有问题。值得注意的是Spring有两种方式来创建容器（我们不再用上文我们自己编写的Container），一种是ApplicationContext，另外一种是BeanFactory。 ApplicationContext更强大一些，而且使用上两者没有太大区别，所以一般说来都用ApplicationContext。 Spring容器帮助我们维护我们在配置文件中声明的Bean以及它们之间的依赖关系，我们的Bean只需要关注自己的核心业务。 四、面向接口的编程<br />    看了这么多，也许你并没有觉得Spring给开发带来了很多便利。那是因为我举的例子还不能突出Spring的优越之处，接下来我将通过接口编程来体现Spring的强大。<br />    假如现在要求扩展Greeting的功能，要让Speaker用不同的语言来问候，也就是说有不同的Speaker，比如ChineseSpeaker, EnglishSpeaker。那么对上文提到的三种编码方式（代码片段2、3、4）分别加以修改，你会发现很麻烦。假如下次又要加入一个西班牙语，又得重复劳动。很自然的会考虑到使用一个ISpeaker接口来简化工作，更改后的代码如下（这里没有列出接口的相关代码，我想你应该明白怎么写）：<br />代码片段9：<br />public void greet() { <br />  ISpeaker s = new ChineseSpeaker(); <br />  s.sayHello(); <br />} <br />代码片段10：<br />public void greet() { <br />  ISpeaker s = (ISpeaker)context.lookup("ejb/ChineseSpeaker"); <br />  s.sayHello(); <br />} <br />代码片段11：<br />public class Greeting() { <br />  private ISpeaker s; <br />  public Greet(ISpeaker s){ <br />    this.s = s; <br />  } <br />  public void greet(){ <br />    s.sayHello(); <br />  } <br />} <br />    对比三段代码，你会发现，第一种方法还是把具体的Speaker硬编码到代码中了，第二中方法稍微好一点，但是没有本质改变，而第三种方法就不一样了，代码中并没有关于具体Speaker的信息。也就是说，如果下次还有什么改动的话，第三种方法的Greeting类是不需要修改，编译的。根据上文Spring的使用介绍，只需要改动xml文件就能给Greeting注入不同的Speaker了，这样代码的扩展性是不是提高了很多？<br />    关于Spring的接口编程还有很多东西可以去挖掘，后文还会提到有关Spring Proxy的接口编程，我这里先介绍这么多，有兴趣话可以去google更多的资料。<br /><br />五、应用Spring中的切面<br />    Spring生来支持AOP，首先来看几个概念：<br />    1、切面（Aspect）：切面是系统中抽象出来的的某一个功能模块，上文已经有过介绍，这里不再多说。<br />    2、通知（Advice）：通知是切面的具体实现。也就是说你的切面要完成什么功能，具体怎么做就是在通知里面完成的。 这个名称似乎有点让人费解，等后面看了代码就明白了。<br />    3、切入点（Pointcut）：切入点定义了通知应该应用到系统的哪些地方。Spring只能控制到方法（有的AOP框架可以控制到属性）， 也就是说你能在方法调用之前或者之后选择切入，执行额外的操作。<br />    4、目标对象（Target）：目标对象是被通知的对象。它可以是任何类，包括你自己编写的或者第三方类。 有了AOP以后，目标对象就只需要关注自己的核心业务，其他的功能，比如日志，就由AOP框架支持完成。<br />    5、代理（Proxy）：简单的讲，<span class="STYLE2">代理就是将通知应用到目标对象后产生的对象</span>。Spring在运行时会给每个目标对象生成一个代理对象， 以后所有对目标对象的操作都会通过代理对象来完成。只有这样通知才可能切入目标对象。对系统的其他部分来说， 这个过程是透明的，也就是看起来跟没用代理一样。<br />    我为了简化，只介绍这5个概念。通过这几个概念应该能够理解Spring的切面编程了。如果需要深入了解Spring AOP的话再去学习其他概念也很快的。<br />    下面通过一个实际的例子来说明Spring的切面编程。继续上文Greeting的例子，我们想在Speaker每次说话之前记录Speaker被调用了。<br />首先创建一个LogAdvice类：<br />代码片段12：<br />public class LogAdvice implements MethodBeforeAdvice{ <br />   public void before(Method arg0, Object[] arg1, Object arg2)throws Throwable?{ <br />      System.out.println( "Speaker called!"); <br />   } <br />} <br />     这里涉及到一个类，MethodBeforeAdvice，这个类是Spring类库提供的，类似的还有AfterReturningAdvice等等，从字面就能理解它们的含义。先不急着理解这个类，我稍后解释。我们继续看如何把这个类应用到我们的系统中去。<br />代码片段13：<br />&lt;beans&gt; <br />   &lt;bean id="Speaker" class="Speaker"/&gt; <br />   &lt;bean id="Greeting" class="Greeting"&gt; <br />      &lt;property name="speaker"&gt; <br />      &lt;ref bean="SpeakerProxy"/&gt; <br />      &lt;/property&gt; <br />   &lt;/bean&gt; <br />   &lt;bean id="LogAdvice" class="LogAdvice"/&gt; <br />   &lt;bean id="SpeakerProxy" class="org.springframework.aop.framework.ProxyFactoryBean"&gt; <br />      &lt;property name="<span class="STYLE2">proxyInterfaces</span>"&gt; &lt;value&gt;ISpeaker&lt;/value&gt;&lt;/property&gt; <br />      &lt;property name="<span class="STYLE2">interceptorNames</span>"&gt; <br />        &lt;list&gt;&lt;value&gt;LogAdvice&lt;/value&gt; &lt;/list&gt; <br />      &lt;/property&gt; <br />      &lt;property name="<span class="STYLE2">target</span>"&gt; &lt;ref local="Speaker"/&gt; &lt;/property&gt; <br />   &lt;/bean&gt; <br />&lt;/beans&gt; <br />     可以看到我们的配置文件中多了两个bean，一个LogAdvice，另外一个SpeakerProxy。LogAdvice很简单。我着重分析一下SpeakerProxy。这个Bean实际上是由Spring提供的ProxyFactoryBean实现。下面定义了三个依赖注入的属性。<br />    1、proxyInterfactes：这个属性定义了这个Proxy要实现哪些接口，可以是一个，也可以是多个（多个的话，要用list标签）。我前面讲过Proxy是在运行是动态创建的，那么这个属性就告诉Spring创建这个Proxy的时候实现哪些接口。<br />    2、interceptorNames：这个属性定义了Proxy被切入了哪些通知，这里只有一个LogAdvice。<br />    3、target：这个属性定义了被代理的对象。在这个例子中target是Speaker。<br />    这样的定义实际上约束了被代理的对象必须实现一个接口，这与上文讲的面向接口的编程有点类似。其实可以这样理解， 接口的定义可以让系统的其他部分不受影响，以前用ISpeaker接口来调用，现在加入了Proxy还是一样的。 但实际上内容已经不一样了，以前是Speaker，现在是一个Proxy。而target属性让proxy知道具体的方法实现在哪里。 Proxy可以看作是target的一个包装。当然Spring并没有强制要求用接口，通过CGLIB（一个高效的代码生成开源类库） 也可以直接根据目标对象生成子类，但这种方式并不推荐。<br />我们还像以前一样的测试我们的Greeting系统，测试代码和代码片段8是一样的。运行结果如下：<br />Speaker called!<br />Hello!<br />    看到效果了吧！而且你可以发现，我们加入Log功能并没有改变以前的代码，甚至测试代码都没有改变，这就是AOP的魅力所在！我们更改的只是配置文件。<br />    下面解释一下刚才落下的MethodBeforeAdvice。关于这个类我并不详细介绍，因为这涉及到Spring中的另外一个概念“连接点（Jointpoint）”， 我详细介绍一个before这个方法。这个方法有三个参数arg0表示目标对象在哪个点被切入了，既然是MethodBeforeAdvice， 那当然是在Method之前被切入了。那么arg0就是表示的那个Method。第二个参数arg1是Method的参数，所以类型是Object[]。 第三个参数就是目标对象了，在Greeting例子中arg2的类型实际上是Speaker。<br />    在Greeting例子中，我们并没有指定目标对象的哪些方法要被切入，而是默认切入所有方法调用（虽然Speaker只有一个方法）。 通过自定义Pointcut，可以控制切入点，我这里不再介绍了，因为这并不影响理解Spring AOP，有兴趣的话去google一下就知道了。<br /><br />六、实战Spring<br />    虽然这部分取名为“实战Spring”，但实际上我并不打算在这里介绍实际开发Spring的内容， 因为我写这篇文章的目的是介绍Spring的概念和用Spring开发的思路，而不是有关Spring的实践和详细介绍。文中介绍的内容和用Spring 做实际开发还相去甚远。之所以取名“实战Spring”是我觉得理解了上文讲的内容以后， 可以开始为深入学习Spring和学习如何在项目中应用Spring了。<br />    要系统的学习Spring还是需要阅读一本详细介绍Spring的书，我推荐Spring in Action，因为我看的就是这本书。 希望这篇文章能为你系统的学习Spring扫除一些障碍。<br /></td>
						</tr>
				</tbody>
		</table>
<img src ="http://www.blogjava.net/sparkler/aggbug/78447.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/sparkler/" target="_blank">sparkler</a> 2006-11-01 11:54 <a href="http://www.blogjava.net/sparkler/articles/78447.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>