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


百度百科这么说明Spring:


Spring是一个开源框架,Spring是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson 在其著作《Expert One-On-One J2EE Development and Design》中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 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(Plain Ordinary Java Object)简单的Java对象,实际就是普通JavaBeans,是为了避免和EJB混淆所创造的简称。POJO实质上可以理解为 简单的实体类,顾名思义POJO类的作用是方便程序员使用数据库中的数据表,对于广大的程序员,可以很方便的将POJO类当做对象来进行使用,当然也是可以方便的调用其get,set方法。POJO类也给我们在struts框架中的配置带来了很大的方便。
如果你从事Java有一段时间了,或许你会发现很多框架都是通过强迫应用继承它们的类或实现它们的接口从而让应用与框架绑死
   
   
package com.habuma.ejb.session;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
 
public class HelloWorldBean implements SessionBean {
public void ejbActivate() {
}
public void ejbPassivate() {
}
public void ejbRemove() {
}
public void setSessionContext(SessionContext ctx) {
}
// 我们的方法
public String sayHello() {
return "Hello World";
}
public void ejbCreate() {
}
}
我们实现了SessionBean 接口,但是我们并不想实现ejbActivate()方法等,HelloWorldBean 的大部分代码仅仅是为使用EJB 而编写的。这引发出一个问题: 到底是谁为谁服务? SessionBean接口为我服务还是我为它服务?不只是EJB 2这样,其他流行框架也是如此,例如早期版本的Struts、WebWork和Tapestry。这些重量级框架都存在如下问题: 强迫开发者编写大量冗余代码、应用与框架绑定,并且通常难以编写测试代码

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

不妨举个例子,我们采用Spring 技术把HelloWorldBean 类进行重写:
   
   
package com.habuma.spring;
 
public class HelloWorldBean {
public String sayHello() {
return "Hello World";
}
}
HelloWorldBean类没有实现、继承或者导入与Spring API 相关的任何东西。HelloWorldBean 只是一个普通的Java 对象。


2. 依赖注入

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

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

我们举个例子来说:

   
   
package com.sjf.bean;
/**
* 扫地任务
* @author sjf0115
*
*/
public class SweepFloorTask implements Task {
 
@Override
public void doTask() {
System.out.println("正在愉快的扫地....");
}
}

   
   
package com.sjf.bean;
/**
* 工人实体类
* @author sjf0115
*
*/
public class Worker {
// 扫地任务
private SweepFloorTask task;
public Worker(){
task = new SweepFloorTask();
}
// 工人工作
public void work(){
task.doTask();
}
}

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

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

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

为了展示这一点,让我们看一看对上述程序的改进版本:
   
   
package com.sjf.bean;
/**
* 工人实体类
* @author sjf0115
*
*/
public class Worker {
// 任务接口
private Task task;
// Task被注入进来
public Worker(Task task){
this.task = task;
}
// 工人工作
public void work(){
task.doTask();
}
}

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

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

3. 应用切面

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

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

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

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

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

QQ截图20160131204024.png


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

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




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

我们还是以Worker例子来说明,我们都知道工人干活,老板都是要发工资的,干活之前老板说几句鼓励的话,让员工更努力的干活,干完活之后对表现不错的小伙发放奖金。
    
    
package com.sjf.bean;
/**
* 老板实体类
* @author sjf0115
*
*/
public class Boss {
public void beforeDoTask(){
System.out.println("我看好你,加油,干完活,多给你发工资...");
}
public void afterDoTask(){
System.out.println("真是一个不错的小伙,这是给你的奖励,继续努力...");
}
}

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

    
    
package com.sjf.bean;
/**
* 工人实体类
* @author sjf0115
*
*/
public class Worker {
// 任务接口
private Task task;
// 老板
private Boss boss;
// Task被注入进来
public Worker(Task task, Boss boss){
this.task = task;
this.boss = boss;
}
// 工人工作
public void work(){
// 干活之前
boss.beforeDoTask();
// 干活
task.doTask();
// 干活之后
boss.afterDoTask();
}
}

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

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

把Boss抽象为一个切面,你所做的事情只是在一个Spring 配置文件中声明它。
    
    
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="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">
<!-- 员工 -->
<bean id="worker" class="com.sjf.bean.Worker">
<constructor-arg ref="sweepFloorTask" />
</bean>
<!-- 扫地任务 -->
<bean id="sweepFloorTask" class="com.sjf.bean.SweepFloorTask" />
<!-- 烧水任务 -->
<bean id="boilWaterTask" class="com.sjf.bean.BoilWaterTask" />
<!-- 老板 -->
<bean id = "boss" class = "com.sjf.bean.Boss" />
<aop:config>
<aop:aspect ref = "boss">
<!-- 定义切面 -->
<aop:pointcut expression="execution(* *.work(..))" id="doTask"/>
<!-- 声明前置通知 -->
<aop:before method="beforeDoTask" pointcut-ref="doTask"/>
<!-- 声明后置通知 -->
<aop:after method="afterDoTask" pointcut-ref="doTask"/>
</aop:aspect>
</aop:config>
</beans>

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

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

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

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

其次,也是最重要的,Boss可以被应用到Worker 中,而Worker 不需要显式地调用它。实际上,Worker完全不知道Boss的存在。
    
    
package com.sjf.bean;
/**
* 工人实体类
* @author sjf0115
*
*/
public class Worker {
// 任务接口
private Task task;
// Task被注入进来
public Worker(Task task){
this.task = task;
}
// 工人工作
public void work(){
// 干活
task.doTask();
}
}

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


来源于:《Spring实战》


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@SmartSi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值