切面(aspects)应用

  DI能够让你的软件组件间保持松耦合,而面向切面编程(AOP)能够让你捕获到在整个应用中可重用的组件功能。在软件系统中,AOP通常被定义为提升关注点分离的一个技术。系统由很多的组件组成,每个组件负责一部分的功能。但是这些组件往往除了核心功能外,还有些额外的责任。比如像日志,事务管理和安全这些系统服务会被引进到组件中。这些服务通常被称为横切关注点(cross-cutting-concerns),因为它们常常贯穿于多个组件中。

  在多个组件中传播这些概念,会有两个复杂层面内容将被引进到你的代码中

  •  实现系统级概念代码将会在多个组件中重复出现,这意味着如果你需要改变这些概念的话,你必须修改多个组件的内容。即使你将这些概念抽象到一个独立的方法中来使它成为一个单一的方法,但是方法的调用还是会出现在多个地方。
  •  你的组件现在充斥着和核心代码无关的功能。

  下图展示的它的复杂度。左边的业务对象和右边的系统服务紧密的关联在一起。

  AOP能够使这些服务成为模块并且很方便的使用它们(声明方式),来达到在组件中消除这些影响。这样让组件更有粘合性,使它们只关注于自己具体的业务,完全忽略系统服务。简而言之,切面让POJOS保持简单。

  你可以将切面设想为覆盖了很多组件的毛毯,如下图所示。一个应用由多个实现业务功能的模块组成。使用AOP,你可以使用这些功能来覆盖你的核心应用。这些层可以通过声明的方式来贯穿到你的应用中,而你核心代码根本不用知道它们的存在。这是一个强大的概念,因为它将应用核心业务和系统服务独立开来。

下面我们来看个具体的例子,现在是个骑士的故事,故事如何流传下来呢,当然是通过歌手的歌声来传递

public class Minstrel {
private PrintStream stream;
public Minstrel(PrintStream stream) {
this.stream = stream;
}
public void singBeforeQuest() {
stream.println("Fa la la, the knight is so brave!");
}
public void singAfterQuest() {
stream.println("Tee hee hee, the brave knight " +
"did embark on a quest!");
}
}

  如你所见,Minstrel是个简单的类并有两个方法。singBeforeQuest()方法在骑士开始探索的之前被调用,而singAfterQuest()方法应当在骑士探索结束后调用。在这两个用例中,Minstrel通过在构造器中注入的PrintSteam来歌颂骑士的事迹。

  现在你只需要注入BraveKnight就能使用,让我们微调下BraveKnight来使用Minstrel。下面展示如何将他们合并在一起

 

public class BraveKnight implements Knight {
private Quest quest;
private Minstrel minstrel;
public BraveKnight(Quest quest, Minstrel minstrel) {
this.quest = quest;
this.minstrel = minstrel;
}
public void embarkOnQuest() throws QuestException {
minstrel.singBeforeQuest();
quest.embark();
minstrel.singAfterQuest();
}
}

  它应该能够运行,你要做的就是回到你的配置文件中,然后声明一个Minstrel bean并且注入到BraveKnight的构造器中。但是等等...是否需要在knight中来管理minstrel?minstrel类不是只要关注于自己的功能?因为knight需要知道minstrel存在。这将迫使你将Minstrel注入到BraveKnight中,这样不仅使BraveKnight的代码变得复杂,而且用户可能需要个没有minstrel的knight。如果Minstrel为空?是否需要进行空值逻辑检测?

  但是如果你使用AOP,minstrel和knight只需要关心各自的事情。要将Minstrel转化成一个切面,你只需要在Spring配置文件中声明它。下面在原来的knights.xml基础上进行修改。

<?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/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="knight" class="com.springinaction.knights.BraveKnight">
  <constructor-arg ref="quest" />
</bean>
<bean id="quest" class="com.springinaction.knights.SlayDragonQuest">
  <constructor-arg value="#{T(System).out}" />
</bean>
<bean id="minstrel" class="com.springinaction.knights.Minstrel">
  <constructor-arg value="#{T(System).out}" />
</bean>
<aop:config>
  <aop:aspect ref="minstrel">
    <aop:pointcut id="embark" expression="execution(* *.embarkOnQuest(..))"/>
    <aop:before pointcut-ref="embark" method="singBeforeQuest"/>
    <aop:after pointcut-ref="embark" method="singAfterQuest"/>
  </aop:aspect>
</aop:config>
</beans>

  上面使用spring aop命名空间配置来声明Minstrel作为一个切面。首先你声明Minstrel作为一个bean。然后在<aop:aspect>元素中引用它。接着进一步对切面定义,你使用<aop:before>来声明在执行embarkOnQuest()执行前调用singBeforeQuest方法,这个叫做前置通知。使用<aop:after>来声明在执行embarkOnQuest()执行后调用singAfterQuest方法,这个叫做后置通知。在这两个用例中,pointcut-ref属性都引用了叫embark的切点。这个切点在前面的<ponitcut>元素中定义,并且它有个叫做expression属性,该属性用来选择那些地方需要使用通知。这个表达式语法是AspectJ的切点表达式语言。

  现在先不要关心AspectJ表达式细节,以后的章节会详细谈论。现在你应该有点感知切面在spring中是如何使用的,是的只要一点点的XML配置,你就能将minstrel转成spring的切面。现在你应该从这个例子中能获取到两点认识。

  1. 首先,Minstrel仍然是一个POJO ---- 没有任何指示能表明它是一个切面。但实际上它却是spring context中的一个切面
  2. 其次,更重要的是,Minstrel可以被用到BraveKnight中,而不用BraveKnight明确的去调用。事实上,BraveKnight完全不关心Minstrel是否存在

这里需要指出的是,在使用Spring魔法将Minstrel转换成一个切面前,需要将它声明为Spring的bean。所以只要注入依赖就能就其他的spring bean也变成一个切面。当然这个例子只是简单的spring aop应用,后面我们将介绍它在声明式事务和安全方面中的应用。接下来我们再来看一种spring使java开发变得更简单的方式。

使用模板消除样板代码

  样板代码指的是你经常一次次编写相似的代码来完成普通、简单的任务。有很多Java APIs存在着样板代码。一个常见的样板代码就是使用JDBC来查询数据库操作。如果你有用过JDBC来操作的话,你可能会经常写下类似下面的一段代码

public Employee getEmployeeById(long id) {
  Connection conn = null;
  PreparedStatement stmt = null;
  ResultSet rs = null;
  try {
    conn = dataSource.getConnection();
    stmt = conn.prepareStatement(
    "select id, firstname, lastname, salary from " +
    "employee where id=?");
    stmt.setLong(1, id);
    rs = stmt.executeQuery();
    Employee employee = null;
  if (rs.next()) {
    employee = new Employee();
    employee.setId(rs.getLong("id"));
    employee.setFirstName(rs.getString("firstname"));
    employee.setLastName(rs.getString("lastname"));
    employee.setSalary(rs.getBigDecimal("salary"));
  }
    return employee;
  } catch (SQLException e) {
  } finally {
    if(rs != null) {
  try {
    rs.close();
  } catch(SQLException e) {}
 }
  if(stmt != null) {
  try {
    stmt.close();
  } catch(SQLException e) {}
 }if(conn != null) {
  try {
    conn.close();
  } catch(SQLException e) {}
  }
}
return null;
}

  如你所见,你只是想查询id为某个值的employee数据,而这里却要写一堆的JDBC操作。首先你要创建个connection,然后创建statement,接着获取查询结果。而且,你还要捕获SQLException异常。最终当所有操作完成后,你必须清除乱七八糟的东西,关闭connection,statement和result set。很显然,上面的代码你可能有经历过。查询employee只要一条语句,但是却要进行大量的JDBC样板操作。

  JDBC不是唯一的样板代码,很多活跃的代码也存在类似的问题,比如JMS,JNDI和REST客服端。Spring通过将样板代码封装到模板中来消除这些重复代码。Spring的JDBCTemplate使不用传统的JDBC来操作数据库成为可能。

  举个例子,使用Spring的SimpleJdbcTemplate(利用java 5特性来实现的一个JDBCTemplate),getEmployeeById()方法可以被重写为,如何获取employee数据而不用在考虑JDBC API操作。下面是更新后的代码

public Employee getEmployeeById(long id) {
  return jdbcTemplate.queryForObject(
    "select id, firstname, lastname, salary " +
    "from employee where id=?",
    new RowMapper<Employee>() {
    public Employee mapRow(ResultSet rs,
      int rowNum) throws SQLException {
      Employee employee = new Employee();
      employee.setId(rs.getLong("id"));
      employee.setFirstName(rs.getString("firstname"));
      employee.setLastName(rs.getString("lastname"));
      employee.setSalary(rs.getBigDecimal("salary"));
    return employee;
    }
  },id);
}

  如你所见,这个版本的getEmployeeById()更加的简单,它只关注于如何进行数据库查询employee操作。模板的queryForObject()只需要一个SQL query语句,以及如何将数据封装到domain object的RowMapper,你看不到任何的JDBC样板代码,所有的样板代码都会在模板中进行处理。

  这里我们使用spring DI、aspect、template来使复杂的java开发变得简单。上面我们举例说明如何配置bean和aspect。下面我们就来解释下这些文件如何加载以及将他们加载到什么地方去。

spring 第一篇(1-1):让java开发变得更简单(下)的更多相关文章

  1. spring 第一篇(1-1):让java开发变得更简单(下)转

    spring 第一篇(1-1):让java开发变得更简单(下) 这个波主虽然只发了几篇,但是写的很好 上面一篇文章写的很好,其中提及到了Spring的jdbcTemplate,templet方式我之前 ...

  2. spring 第一篇(1-1):让java开发变得更简单(上)

    1.释放POJOS能量 传统开发中是如何束缚POJOS呢,如果你开发过java很长时间,那你一定有接触过EJB的开发.那时候开发一个小小的功能都要扩展框架的类或者实现其接口.所以你很容易在早期的Str ...

  3. GVIM与模板——让FPGA开发变得更简单

    还在使用FPGA开发环境自带的代码编辑器?还在逐个字母敲击冗长重复的代码?明德扬至简设计法让你快速提高代码编写效率!利用GVIM这一高效的编辑工具并添加自定义模板,通过简短的脚本命令即可自动生成所有常 ...

  4. .net敏捷开发框架 力软敏捷开发(learun) 让开发变的更简单

    版本:6.1.6.2 体验地址:www.fishcmonkey.com 联系QQ:6539471

  5. .net敏捷开发框架 力软(learun) 让开发变的更简单

    版本:6.1.6.2 体验地址:www.fishcmonkey.com 联系QQ:6539471

  6. spring 第一篇(1-3):鸟瞰spring蓝图

    如你所见,spring框架的核心是关注于如何使用DI.AOP和模板来让企业级java开发变得更简单.spring确实也是这样做的,所以很值得你去使用它.不过spring内容可能比你所能看到的要多很多. ...

  7. 第一篇:微信公众平台开发实战Java版之了解微信公众平台基础知识以及资料准备

    相信很多人或多或少听说了微信公众平台的火热.但是开发还是有一点门槛,鉴于挺多朋友问我怎么开发,问多了,自己平时也进行以下总结.所以下面给大家分享一下我的经验: 微信公众号是什么? 官网的介绍:再小的个 ...

  8. JVM学习第一篇思考:一个Java代码是怎么运行起来的-上篇

    JVM学习第一篇思考:一个Java代码是怎么运行起来的-上篇 作为一个使用Java语言开发的程序员,我们都知道,要想运行Java程序至少需要安装JRE(安装JDK也没问题).我们也知道我们Java程序 ...

  9. 初学Java ssh之Spring 第一篇

    之前虽然毕业前实习的工作是使用的C# .NET语言,但是,毕业后还是果断应聘Java.虽然自己对Java的理解不如C#深入,只是对基础知识比较熟悉,但还是义无返顾了··· 虽然应聘经历比较坎坷,但最终 ...

随机推荐

  1. Hibernate之Query接口的uniqueResult()方法

    如果查询返回多个值用list()方法 public void testQuery(){ Configuration config = new Configuration().configure(); ...

  2. AC日记——信息传递 洛谷 P2661 (tarjan求环)

    题目描述 有n个同学(编号为1到n)正在玩一个信息传递的游戏.在游戏里每人都有一个固定的信息传递对象,其中,编号为i的同学的信息传递对象是编号为Ti同学. 游戏开始时,每人都只知道自己的生日.之后每一 ...

  3. Android 屏幕适配(一)百分比布局库(percent-support-lib) 解析与扩展

    转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/46695347: 本文出自:[张鸿洋的博客] 一.概述 周末游戏打得过猛,于是周 ...

  4. SVN和git的使用(附github的简单玩法)

    今天简单的总结了下SVN和git的使用,也尝试了下github,应该好好提高下自己的英文水平了,梦想有一天不再使用任何翻译软件. [svn]:集中式的代码管理工具(版本控制工具--版本记录) 1> ...

  5. 003医疗项目-关于<context:property-placeholder location="classpath:db.properties"/>的问题

    项目结构如下:

  6. 课程2——变量修饰关键字

    声明:本系列随笔主要用于记录c语言的常备知识点,不能保证所有知识正确性,欢迎大家阅读.学习.批评.指正!!你们的鼓励是我前进的动力.严禁用于私人目的.转载请注明出处:http://www.cnblog ...

  7. 【Andorid------手势识别】GestureDetector和SimpleOnGestureListener的使用教程(转)——

    FROM:http://www.cnblogs.com/transmuse/archive/2010/12/02/1894833.html 1. 当用户触摸屏幕的时候,会产生许多手势,例如down,u ...

  8. How to configure SRTM elevations in WorldWind WMS

    In this thread I will try to explain how to serve SRTM elevations using NASA WorldWind WMS. ! Import ...

  9. Linux经常用到的命令

    1. Linux下用vim打开配置文件乱码,在终端输入:“LANG=”即可. 2. 查看端口是否被占用: 3. netstat -anp | grep port netstat -ltn 4. lso ...

  10. WebSocket使用教程 2

    WebSocket使用教程 - 带完整实例 收藏 james_laughing 发表于 2年前 阅读 46438 收藏 23 点赞 5 评论 4 摘要: WebSocket使用教程 - 带完整实例 什 ...