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