您的位置:首页 > 编程语言 > Java开发

[Spring实战系列](3)开启Spring之门

2016-01-31 23:01 288 查看
百度百科这么说明Spring:

Spring是一个开源框架,Spring是于2003年兴起的一个轻量级的Java
开发框架,由RodJohnson在其著作《ExpertOne-On-OneJ2EEDevelopmentandDesign》中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为J2EE
应用程序开发提供集成的框架。Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。Spring的核心是控制反转(IoC)面向切面(AOP)。简单来说,Spring是一个分层的JavaSE/EEfull-stack(一站式)
轻量级开源框架。

Spring可以做很多事情,但归根到底,支撑Spring的是少许的基本理念,所有的理念都可以追溯到Spring的最根本的使命上:简化Java开发

那么Spring是如何简化Java开发的呢?为了降低Java开发的复杂性,Spring采取了以下4种关键策略:

基于POJO的轻量级和最小侵入性编程
通过依赖注入和面向接口实现松耦合
基于切面和惯例进行声明式编程
通过切面和模板减少样板式代码

几乎Spring所做的任何事情都可以追溯到上述的一条或者多条核心策略上。

1.激发POJO的潜能

POJO(PlainOrdinaryJavaObject)简单的Java对象,实际就是普通JavaBeans,是为了避免和EJB混淆所创造的简称。POJO实质上可以理解为简单的实体类,顾名思义POJO类的作用是方便程序员使用数据库中的数据表,对于广大的程序员,可以很方便的将POJO类当做对象来进行使用,当然也是可以方便的调用其get,set方法。POJO类也给我们在struts框架中的配置带来了很大的方便。

如果你从事Java有一段时间了,或许你会发现很多框架都是通过强迫应用继承它们的类或实现它们的接口从而让应用与框架绑死。

[code]packagecom.habuma.ejb.session;
importjavax.ejb.SessionBean;
importjavax.ejb.SessionContext;
publicclassHelloWorldBeanimplementsSessionBean{
publicvoidejbActivate(){
}
publicvoidejbPassivate(){
}
publicvoidejbRemove(){
}
publicvoidsetSessionContext(SessionContextctx){
}
//我们的方法
publicStringsayHello(){
return"HelloWorld";
}
publicvoidejbCreate(){
}
}


我们实现了SessionBean接口,但是我们并不想实现ejbActivate()方法等,HelloWorldBean的大部分代码仅仅是为使用EJB而编写的。这引发出一个问题:到底是谁为谁服务?SessionBean接口为我服务还是我为它服务?不只是EJB2这样,其他流行框架也是如此,例如早期版本的Struts、WebWork和Tapestry。这些重量级框架都存在如下问题:强迫开发者编写大量冗余代码、应用与框架绑定,并且通常难以编写测试代码。

Spring却竭力避免这一现象,Spring不会强迫你实现Spring规范的接口或继承Spring规范的类,相反,在基于Spring构建的应用中,它的类通常没有任何痕迹表明你使用了Spring。最坏的场景是,一个类或许会使用Spring注解,但它依旧是POJO。

不妨举个例子,我们采用Spring技术把HelloWorldBean类进行重写:

[code]packagecom.habuma.spring;
publicclassHelloWorldBean{
publicStringsayHello(){
return"HelloWorld";
}
}


HelloWorldBean类没有实现、继承或者导入与SpringAPI相关的任何东西。HelloWorldBean只是一个普通的Java对象。

2.依赖注入

咋听之下依赖注入这个词让人望而生畏,现在已经演变成一项复杂编程技巧或设计模式理念。但事实证明,依赖注入并不像它听上去那么复杂。在项目中应用依赖注入,你会发现代码会变得异常简单、更容易理解和更易于测试。

任何一个有实际意义的应用(肯定比HelloWorld示例更复杂)都是由两个或者更多的类组成,这些类相互之间进行协作来完成特定的业务逻辑。通常,每个对象负责管理与自己相互协作的对象(即它所依赖的对象)的引用,这将会导致高度耦合和难以测试的代码。

我们举个例子来说:

[code]packagecom.sjf.bean;
/**
*扫地任务
*@authorsjf0115
*
*/
publicclassSweepFloorTaskimplementsTask{
@Override
publicvoiddoTask(){
System.out.println("正在愉快的扫地....");
}
}


[code]packagecom.sjf.bean;
/**
*工人实体类
*@authorsjf0115
*
*/
publicclassWorker{
//扫地任务
privateSweepFloorTasktask;
publicWorker(){
task=newSweepFloorTask();
}
//工人工作
publicvoidwork(){
task.doTask();
}
}


我们可以看到Worker(工人)在自己的构造方法中自己创建了一个SweepFloorTask(扫地任务),这使得Worker与SweepFloorTask紧密的耦合在一起,因此极大的限制了这个Worker的工作能力,目前这个工人只能执行扫地的任务,给他赋予烧水的任务都不行,一根筋的就知道扫地,其他爱莫能助。

耦合具有两面性。一方面,紧密耦合的代码难以测试,难以复用,难以理解,并且典型地表现出“打地鼠”式的bug特性(修复一个bug,导致出现一个新的或者甚至更多的bug)。另一方面,一定程度的耦合又是必须的,完全没有耦合的代码什么也做不了。耦合是必须的,但应当小心谨慎地管理它。

另一种方式,通过依赖注入(DI),对象的依赖关系将由负责协调系统中各个对象的第三方组件在创建对象时设定。对象无需自行创建或管理它们的依赖关系,依赖关系将被自动注入到需要它们的对象中去。

为了展示这一点,让我们看一看对上述程序的改进版本:

[code]packagecom.sjf.bean;
/**
*工人实体类
*@authorsjf0115
*
*/
publicclassWorker{
//任务接口
privateTasktask;
//Task被注入进来
publicWorker(Tasktask){
this.task=task;
}
//工人工作
publicvoidwork(){
task.doTask();
}
}


不同于之前的Worker(工人),这个Worker没有自行创建任务,而是在构造时把所要执行的任务作为构造器参数传入。这是依赖注入的方式之一,即构造器注入。更重要的是,它被传入的任务类型是Task接口,而不是某个具体的任务(如扫地,烧水任务等)。所以,这个Worker能够执行SweepFloorTask(扫地),BoilWaterTask(烧水)等任意一种Task实现。

最重要的是本Worker没有与任何特定的Task实现发生耦合。对它来说,被要求执行的任务只要实现了Task接口,那么具体是哪一类型的任务就无关紧要了。这就是依赖注入最大的好处-----松耦合。如果一个对象只通过接口(而不是具体实现或初始化的过程)来表明依赖关系,那么这种依赖就能够在对象本身毫不知情的情况下,用不同的具体实现进行替换。

3.应用切面

依赖注入让相互协作的软件组件保持松散耦合,而AOP编程允许你把遍布应用各处的功能分离出来形成可重用的组件

系统由许多不同组件组成,每一个组件各负责一块特定功能。除了实现自身核心的功能之外,这些组件还经常承担着额外的职责。诸如日志、事务管理和安全此类的系统服务,并且经常融入到有自身核心业务逻辑中去,这些系统服务通常被称为横切关注点,因为它们总是跨越系统的多个组件。

如果将这些横切关注点分散到多个组件中去,你的代码将引入双重复杂性:

遍布系统的关注点实现代码将会重复出现在多个组件中。这意味着如果你要改变这些关注点的逻辑,你必须修改各个模块的相关实现。即使你把这些关注点抽象为一个独立的模块,其他模块只是调用它的方法,但方法的调用还是重复出现在各个模块中;

你的组件会因为那些与自身核心业务无关的代码而变得混乱。

左边的业务对象与系统级服务结合的过于紧密。每个对象不但要知道它需要记日志、进行安全控制和参与事务,还要亲自执行这些服务。





AOP使这些服务模块化,并以声明的方式将它们应用到它们需要影响的组件中去。结果是这些组件具有更高内聚性以及更加关注自身业务,完全不需要了解可能涉及的系统服务的复杂性。总之,AOP确保POJO保持简单。

我们可以把切面想象为覆盖在很多组件之上的一个外壳。应用是由那些实现各自业务功能的模块组成。利用AOP,你可以使用各种功能层去包裹核心业务层。这些层以声明的方式灵活应用到你的系统中,甚至你的核心应用根本不知道它们的存在。这是一个非常强大的理念,可以将安全、事务和日志关注点与你的核心业务逻辑相分离。





为了示范在Spring中如何应用切面,让我们重新回到骑士的例子,并为它添加一个切面。

我们还是以Worker例子来说明,我们都知道工人干活,老板都是要发工资的,干活之前老板说几句鼓励的话,让员工更努力的干活,干完活之后对表现不错的小伙发放奖金。

[code]packagecom.sjf.bean;
/**
*老板实体类
*@authorsjf0115
*
*/
publicclassBoss{
publicvoidbeforeDoTask(){
System.out.println("我看好你,加油,干完活,多给你发工资...");
}
publicvoidafterDoTask(){
System.out.println("真是一个不错的小伙,这是给你的奖励,继续努力...");
}
}


正如你所看到的那样,Boss(老板)是一个只有两个方法的简单的类。在员工执行每一个任务之前,beforeDoTask()方法被调用;在员工完成任务之后,afterDoTask()方法被调用。

[code]packagecom.sjf.bean;
/**
*工人实体类
*@authorsjf0115
*
*/
publicclassWorker{
//任务接口
privateTasktask;
//老板
privateBossboss;
//Task被注入进来
publicWorker(Tasktask,Bossboss){
this.task=task;
this.boss=boss;
}
//工人工作
publicvoidwork(){
//干活之前
boss.beforeDoTask();
//干活
task.doTask();
//干活之后
boss.afterDoTask();
}
}


这应该可以达到预期效果,但总感觉有些东西不太对。老板应该做他份内的事,根本不需要员工命令他这么做。为什么员工还需要提醒吟老板去做他份内的事情呢(工作之前演讲,工作之后发放奖金)?

利用AOP,可以声明老板在员工干活之前讲话鼓励大家好好工作,干完活之后对表现突出的员工发放奖励,而Worker(员工)就不再直接访问Worker(老板)的方法了。

把Boss抽象为一个切面,你所做的事情只是在一个Spring配置文件中声明它。

[code]<?xmlversion="1.0"encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<!--员工-->
<beanid="worker"class="com.sjf.bean.Worker">
<constructor-argref="sweepFloorTask"/>
</bean>
<!--扫地任务-->
<beanid="sweepFloorTask"class="com.sjf.bean.SweepFloorTask"/>
<!--烧水任务-->
<beanid="boilWaterTask"class="com.sjf.bean.BoilWaterTask"/>
<!--老板-->
<beanid="boss"class="com.sjf.bean.Boss"/>
<aop:config>
<aop:aspectref="boss">
<!--定义切面-->
<aop:pointcutexpression="execution(**.work(..))"id="doTask"/>
<!--声明前置通知-->
<aop:beforemethod="beforeDoTask"pointcut-ref="doTask"/>
<!--声明后置通知-->
<aop:aftermethod="afterDoTask"pointcut-ref="doTask"/>
</aop:aspect>
</aop:config>
</beans>


这里使用了Spring的AOP配置的命名空间把BossBean声明为一个切面。首先,必须把Boss声明为一个Bean,然后在<aop:aspect>元素中引用该Bean。为了进一步定义切面,必须使用<aop:before>来声明在work()方法执行前调用Boss的beforeDoTask()方法。这种方式被称为前置通知。同时还必须使用<aop:after>
声明在work()方法执行后调用afterDoTask()方法。这种方式被称为后置通知

在这两种方式中,pointcut-ref属性都引用了名为doTask的切入点。该切入点是在前边的<pointcut>元素中定义的,并配置expression属性来选择所应用的通知。表达式的语法采用了AspectJ的切点表达式语言。

你无需担心你不了解AspectJ或者编写AspectJ切点表达式语言的细节,我们会在后面介绍SpringAOP的内容。现在你已经可以理解如何让Spring在员工执行任务前后来调用Boss的beforeDoTask()和afterDoTask()方法。

这就是所做的一切!通过少量的XML配置,你就可以把Boss声明为一个Spring切面。首先,Boss仍然是一个POJO,没有任何代码表明它要被作为一个切面使用。当我们按照上面那样配置后,在Spring的上下文中,Boss实际上已经变成一个切面了。

其次,也是最重要的,Boss可以被应用到Worker中,而Worker不需要显式地调用它。实际上,Worker完全不知道Boss的存在。

[code]packagecom.sjf.bean;
/**
*工人实体类
*@authorsjf0115
*
*/
publicclassWorker{
//任务接口
privateTasktask;
//Task被注入进来
publicWorker(Tasktask){
this.task=task;
}
//工人工作
publicvoidwork(){
//干活
task.doTask();
}
}


完整项目下载:点击打开链接(Maven创建项目)

来源于:《Spring实战》
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: