用Spring 2.0和AspectJ简化企业应用程序

Spring:简单而强大
Spring的目标是使企业应用程序开发尽可能地简单和高效。这一 理论的实例可以从Spring的JDBC、ORM、JMX、依赖注入等方法,以及企业应用程序开发的其他许多重要领域中见到。Spring还区分了使事情 简单化和过分单纯化之间的差异。最不可思议的是同时提供了简单化和强大的功能。企业应用程序中复杂性的一个根源来自影响应用程序多个部分的特性和需求的实 现。相关于这些特性的代码最终散布在应用程序代码中,使得它更难以添加、维护和理解。Spring 2.0使得以模块化的方式实现这些特性变得更加简单,极大地简化了整体的应用程序代码,并且有时使得在实现没有它的情况下十分痛苦的编码需求变得易如反 掌。

事务管理是影响应用程序多个部分的一个特性实例:一般来说所有的操作都在服务层。在Spring中解决这种需求的方式是通过使用 AOP。Spring 2.0在它对AOP的支持中提供了一个明显的简化,同时还提供了比Spring 1.x所提供的更多富有表现力的功能。这些改善之处主要来自两个主要的领域:通过使用XML schema极大地简化了配置,以及与AspectJ的整合带来了更好的富有表现力的功能和更简单的advice模型。

在本文中,我将首先 介绍在典型的企业应用程序中,Spring AOP和AspectJ适用于什么地方,之后介绍在2.0中新的Spring AOP支持。大部分篇幅用来讲解企业应用程序中AOP的采用路线图,通过大量可以只用AOP实现的特性实例,但是用任何其他的方法进行实现都将非常困难。

简化企业应用程序
典型的企业应用程序——比如一个Web应用程序——由许多层构成。一个包含视图和控制器的Web层,一个表现系统业务接口的服务层,一个负责保存和获取持久化领域对象的数据访问或者存储层,与所有这些层共事的,还有一个核心业务逻辑所在的领域模型。

Web 层、服务层和数据访问层有着许多相同的重要特征:它们应该尽可能地瘦,它们不应该包含业务逻辑,并且它们一般通过Spring组装在一起。在这些层中, Spring负责创建对象和配置。领域模型则有些不同:领域对象由程序员利用新的操作器创建(或者利用从数据库中获取的ORM工具进行扩建)。领域对象有 许多唯一的实例,它们(可以)有丰富的行为。

服务层可以包含特定于应用程序用例的逻辑,但是所有领域相关的逻辑都应该放在领域模型本身里面。

服务层一般是使用声明式企业服务(例如事务)的地方。声明式的企业服务,例如事务和安全是影响应用程序中多个点的很好的需求实例。事实上,即使你想让(比如)事务划分只在单个地方,将这项功能与你的应用程序逻辑分开,使得代码更加简单,避免不必要的耦合,这也仍然很好。

由 于服务对象是Spring管理的bean,Spring AOP天生适合于在这个层中处理需求。事实上,任何人在使用Spring的声明式事务支持时,就已经是在使用Spring AOP了,无论他们是否意识到这一点。Spring AOP很成熟,得到了广泛的应用。它非常适合于Web、服务和数据访问层中受Spring管理的bean,只要你的需求可以通过advice bean方法执行得到处理(且这些层的许多用例都属于这一类)。

当提到影响你领域模型中多个点的需求时,你应用程序的最重要部分——Spring AOP——的帮助就小多了。你可以编 程式地使用Spring AOP,但是这样会很难使用,并且还要你自己负责创建代理和管理同一性。AspectJ天生适合于实现影响领域对象的特性。AspectJ方面不需要任何 特殊的代理创建,并且可以很恰当地通知运行时在你的应用程序代码中,或者通过你可能使用的框架所创建的对象。当你想要模块化影响你应用程序的所有不同层的 行为,或者模块化性能以任何方式感知的行为时,AspectJ也是一种非常好的解决方案。

因此,我们最想要的是一种一致的Spring AOP和AspectJ方法,以便我们可以很容易地一起使用这两种工具,以便如果需求发生变化,你用(比如)Spring AOP开发的能力就可以转移到AspectJ上。无论我们正在使用哪种组合,我们仍然喜欢依赖注入和Spring所提供的配置的所有益处。Spring 2.0中新的AOP支持正好带来了这一点。

底层的技术:AspectJ和Spring AOP简介
AOP使得实现在应用程序中影响多个点的特性变得更加简单。这主要因为AOP提供了对名为通知(advice)的这个东西的支持。通知不同于必须显式调用的方法,每当发生匹配的触发事件时,它就自动地执行。继续事务主题,触发事件是服务层中一个方法的执行,并且通知逻辑提供所需的事务划分。用AOP的话来说,触发事件被称作连接点(join point),而切入点表达式(pointcut expression)则 用来选择通知要在那里运行的连接点。这个简单的倒置意味着不用将调用散布到你全部应用程序代码中的事务管理器,而是只要编写一个切入点表达式,定义你需要 事务管理器在什么地方完成某事的所有点,并将它与适当的通知关联起来。AspectJ和Spring AOP提供对这个模型的支持,事实上,它们有着完全相同的切入点表达语言。

在接下来的讨论中,注意Spring和AspectJ保持为独立 的工程,这很重要。Spring只使用反射和由AspectJ 5作为一个库所暴露的工具API。Spring 2.0仍然是一个运行时基于代理的框架,且AspectJ织入器(weaver)不用于Spring方面。

我相信你们中大多数人都知道, AspectJ是一种包含完整编译器的语言(构建为Eclipse JDT Java编译器的一个扩展),对离线或者在运行时将(与)二进制的class文件(链接的方面)作为类织入的支持,被加载到了虚拟机中。AspectJ的 最新发布版本是AspectJ 5,它为Java 5语言提供完整的支持。

AspectJ 5也引入了方面声明的第二种风格,我们称之为“@AspectJ”,它允许你将一个方面编写为一个包含注解的Java类。这种方面可以通过一般的Java 5编译器进行编译。例如,传统的“HelloWorld”方面在AspectJ编程语言中看起来像这样:

public aspect HelloFromAspectJ { pointcut mainMethod() : execution(* main(..)); after() returning : mainMethod() { System.out.println("Hello from AspectJ!); }}
与传统的HelloWorld类共同编译这个方面,当你运行应用程序时,会看到这样的输出:

Hello World!Hello from AspectJ!
我们可以用@Aspect风格编写相同的方面如下:

@Aspectpublic class HelloFromAspectJ { @Pointcut("execution(* main(..))") public void mainMethod() {} @AfterReturning("mainMethod()") public void sayHello() { System.out.println("Hello from AspectJ!"); }}
就 本文而言,AspectJ 5中另一项重要的新特性是一个完全AspectJ感知的反射API(你可以在运行时为它的通知和切入点成员等等请求一个方面),和让第三方使用 AspectJ的切入点解析和匹配引擎的工具API。这些API的第一大用户,就像你很快会见到的,是Spring AOP。

与AspectJ 相反,Spring AOP是一个基于代理的运行时框架。在使用Spring AOP时,并没有特殊的工具或者构建需求,因而Spring AOP是一种很容易开始的方法。作为一种基于代理的框架,它既有优点也有缺点。除了已经提到过的容易使用的因素之外,基于代理的框架还能够独立地通知相同 类型的不同实例。将这一点与AspectJ基于类型的语义相比,在这里,类型的每一个实例都有着相同的行为。对于像Spring这样的框架而言,能够独立 地通知独立的对象(Spring beans)是一个重要的必要条件。另一方面,Spring AOP只支持AspectJ功能的一个子集:有可能在Spring beans中通知方法的执行,但是其他没什么。

基于代理的框架一般会有 同一性的问题:有两个对象(代理和目标)都表示应用程序中的同一个实体。必须始终小心地传递适当的引用,确保给实例化过的任何新的目标对象创建代理。 Spring AOP通过管理bean实例化(以便代理可以被透明地创建)和通过依赖注入(以便Spring始终可以注入适当的引用),巧妙地解决了这些问题。

Spring 2.0中新的AOP支持
2.0 中的Spring AOP可以完全向后与Spring 1.x应用程序和配置兼容。它还提供了比Spring 1.x更简单且更强大的配置。新的AOP支持是基于schema的,因此在你的Spring beans配置文件中将需要相关的命名空间和schema定位属性。它看起来像这样:

<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.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> ...</beans>
与使用DTD时所需要的更简单的xml配置相比,那么目前为止我们还没有超越——但这是标准的xml配置,并且可以在你的IDE中的一个模板里创建,并且只在每当你需要创建一个Spring配置时才被重用。当我们开始将一些内容添加到配置中时,你会领略到这一好处。

Spring 2.0默认使用AspectJ 切入点语言(受执行连接点种类的限制)。如果它看到一个AspectJ 切入点表达式,它就调出AspectJ对它进行解析和匹配。这意味着你用Spring AOP编写的任何切入点表达式都将以与AspectJ完全相同的方式进行工作。此外,Spring实际上能理解@AspectJ方面,因此有可能共用 Spring和AspectJ之间完整的方面定义。激活这项功能很容易,只要将<aop:aspectj-autoproxy>元素包括在你 的配置中。如果AspectJ自动代理以这种方式激活,那么在你的应用程序上下文中定义的、包含@AspectJ方面的任何bean,都将被Spring AOP视为一个方面,并将相应地通知上下文中的bean。

下面是当你以这种方式使用Spring AOP时的Hello World程序。首先,应用程序上下文文件中bean元素的内容:

<bean id="helloService" class="org.aspectprogrammer.hello.spring.HelloService"/> <aop:aspectj-autoproxy/> <bean id="helloFromAspectJ" class="org.aspectprogrammer.hello.aspectj.HelloFromAspectJ"/>
HelloService是一个简单的Java类:

public class HelloService { public void main() { System.out.println("Hello World!"); }}
HelloFromAspectJ与你在本文前面见过的被注解的Java类(@AspectJ方面)完全相同。以下是启动Spring容器的一个小主类,获得一个对helloService bean的引用,并在它上面调用’main’方法:

public class SpringBoot { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext( "org/aspectprogrammer/hello/spring/application-context.xml"); HelloService service = (HelloService) context.getBean("helloService"); service.main(); }}
运行这个程序产生下面的输出:

Hello World!Hello from AspectJ!
记住,这仍然是Spring AOP(我们根本没有在使用AspectJ编译器或者织入器),但它是提供关于@AspectJ方面的反射信息和解析并匹配代表Spring的切入点的AspectJ。

Spring 2.0还支持用一个简单的POJO支持的方面声明的一种xml形式(不需要任何注解)。xml形式也使用相同的AspectJ 切入点语言子集,并支持相同的五种AspectJ 通知类型(前置通知(before advice)、后置通知(after returning advice)、异常通知(after throwing advice)、后通知(after [finally] advice)和 环绕通知(around advice))。

下面是使用一个基于XML的方面声明的hello world应用程序:

<bean id="helloService" class="org.aspectprogrammer.hello.spring.HelloService"/> <aop:config> <aop:aspect ref="helloFromSpringAOP"> <aop:pointcut id="mainMethod" expression="execution(* main(..))"/> <aop:after-returning pointcut-ref="mainMethod" method="sayHello"/> </aop:aspect> </aop:config> <bean id="helloFromSpringAOP" class="org.aspectprogrammer.hello.spring.HelloAspect"/>
aop 命名空间中的元素可以用来声明方面、切入点和通知,有着与它们的AspectJ和@AspectJ等效物完全相同的语义。“aspect”元素引用 Spring bean(完全由Spring配置和实例化),并且每个通知元素都在该bean中指定将被调用来执行通知的方法。在这个例子中,HelloAspect类 只是:

public class HelloAspect { public void sayHello() { System.out.println("Hello from Spring AOP!"); }}
运行程序将产生熟悉的输出:

Hello World!Hello from Spring AOP!
如果你还没有编写过这样的程序,就下载Spring 2.0,亲自尝试一下,这可是个好主意。

我 不想把本文变成是关于Spring AOP的一个完全的教程,而是想要加紧看一些可以有效地以这种方式实现的特性实例。我将只是指出,传递Spring从使用AspectJ 切入点语言中获得的其中某个东西,是编写静态类型的通知(声明它们真正需要的那些参数的方法)的能力,与始终使用非类型的Object数组相反——这使得 通知方法更容易编写。

采用路线图
理论说得够多了……让我们看一下你在企业应用程序中实际上如何以及为什么要使用AOP的一些例子。开始AOP,并不一定是一种肯定一切或者否定一切的爆炸性方法。采用可以分阶段进行,每个阶段都为增加的技术暴露回报以更多的益处。

建 议的采用路线图是只开始使用Spring提供的开箱即用的方面(例如事务管理)。许多Spring用户将已经在这么做了,但多半不太欣赏AOP被“背地 里”使用着。根据这一点,你可以实现在使用Spring AOP的Web、服务和数据访问层中可能会有的任何定制横切需求。

实现影响领域模 型的特性必需使用AspectJ。你听到这句话时可能感到惊讶:有大量的AspectJ方面对于你在开发时都非常有帮助,而且不影响在产品中以任何方式运 行的应用程序。这些方面可以增加很多价值,并且采用风险非常小,因此建议用它们开始AspectJ。根据这一点,你可以选择通过AspectJ实现“基础 结构的”需求——典型的实例为剖析(profiling)、跟踪(tracing)、错误处理(error-handling)等等。随着你越来越习惯于 AspectJ和所配套的工具,最终你可以用方面在领域逻辑自身中开始实现功能。

关于AOP采用路线图的其他信息,请见《Eclipse AspectJ》一书中的第11章,或者developerWorks AOP@Work系列中“Next steps with aspects”一文。这两个资源都专门关注AspectJ,而我在这里则正在讨论同时使用Spring和AspectJ。

让我们依次看一下这每一种采用阶段。

当 在一个工程中使用AOP时,首先要做的最有意义的事是定义一组切入点表达式,描述你应用程序中的不同模块或者层。这些切入点表达式在采用的所有不同阶段中 都将很有帮助,并且定义一次将减少重复,改善代码的清晰度。如果我们用@AspectJ符号编写这些切入点,它们就可以通过任何常规的Java 5编译器进行编译。利用一般的AspectJ语言关键字也可能编写相同的东西,用ajc编译源文件,并将生成的.class文件添加到classpath 中。我将用@AspectJ作为开始Spring AOP的两种方法中更为容易的那一种。许多读者将会熟悉Spring所携带的“jpetstore”范例应用程序。我已经稍微重写了这个应用程序,给它增 加了一些方面(本文稍后会讨论到)。以下是在pet store中捕捉主要层和模块的“SystemArchitecture”方面的开头部分:

@Aspectpublic class SystemArchitecture { /** * we're in the pet store application if we're within any * of the pet store packages */ @Pointcut("within(org.springframework.samples.jpetstore..*)") public void inPetStore() {} // modules // =========== @Pointcut("within(org.springframework.samples.jpetstore.dao..*)") public void inDataAccessLayer() {} @Pointcut("within(org.springframework.samples.jpetstore.domain.*)") public void inDomainModel() {} @Pointcut("within(org.springframework.samples.jpetstore.service..*)") public void inServiceLayer() {} @Pointcut("within(org.springframework.samples.jpetstore.web..*)") public void inWebLayer() {} @Pointcut("within(org.springframework.samples.jpetstore.remote..*)") public void inRemotingLayer() {} @Pointcut("within(org.springframework.samples.jpetstore.validation..*)") public void inValidationModule() {} // module operations // ================== @Pointcut("execution(* org.springframework.samples.jpetstore.dao.*.*(..))") public void doaOperation() {} @Pointcut("execution(* org.springframework.samples.jpetstore.service.*.*(..))") public void businessService() {} @Pointcut("execution(public * org.springframework.samples.jpetstore.validation.*.*(..))") public void validation() {}}
既然我们已经有了谈论应用程序(“inServiceLayer”、“businessOperation”等等)的术语,让我们用它来做一些有意义的事情吧。

使用开箱即用的Spring方面
advisor是Spring 1.x遗留下来的一个Spring概念,它包含了一个非常小的方面,带有单独的一条通知,和关联的切入点表达式。对于事务划分而言,advisor就是我 们所需要的一切。典型的事务需求为:服务层中的所有操作都要利用(几个)底层资源管理器的默认隔离级别在一个事务(REQUIRED语义)中执行。此外, 一些操作可以被标识为“只读”事务——这一知识可以给这类事务带来明显的性能改善。jpestore advisor声明如下:

<!-- all aspect and advisor declarations are gathered inside an aop:config element--><aop:config> <aop:advisor pointcut="org.springframework.samples.jpetstore.SystemArchitecture.businessService()" advice-ref="txAdvice"/></aop:config>
这 个声明仅仅意味着:当执行一个“businessService”时,我们需要运行被“txAdvice”引用的通知。 “BusinessService”切入点在我们前面讨论过的 org.springframework.samples.jpetstore.SystemArchitecture方面中定义。它与在服务接口中定义 的任何操作的执行相匹配。由于事务通知本身可能需要相当多的配置,因此Spring在tx命名空间中提供了tx:advice元素,使得这项工作变得更加 简单和清晰。这就是给jpetstore应用程序的“txAdvice”定义:

<!-- Transaction advice definition, based on method name patterns. Defaults to PROPAGATION_REQUIRED for all methods whose name starts with "insert" or "update", and to PROPAGATION_REQUIRED with read-only hint for all other methods.--><tx:advice id="txAdvice"> <tx:attributes> <tx:method name="insert*"/> <tx:method name="update*"/> <tx:method name="*" read-only="true"/> </tx:attributes></tx:advice>
还有一种更加简单的方法来配置使用注解的事务。在使用@Transactional注解时,你唯一需要的XML是:

<!-- Tell Spring to apply transaction advice based on the presence of the @Transactional annotation on Spring bean types.--><tx:annotation-driven/>
使用注解方法时,PetService实现要做如下注解:

/* * all operations have TX_REQUIRED, default isolation level, * read-write transaction semantics by default */@Transactionalpublic class PetStoreImpl implements PetStoreFacade, OrderService { ... /** * override defaults on a per-method basis */ @Transactional(readOnly=true) public Account getAccount(String username) { return this.accountDao.getAccount(username); } ...}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
完整版:https://download.csdn.net/download/qq_27595745/89522468 【课程大纲】 1-1 什么是java 1-2 认识java语言 1-3 java平台的体系结构 1-4 java SE环境安装和配置 2-1 java程序简介 2-2 计算机中的程序 2-3 java程序 2-4 java类库组织结构和文档 2-5 java虚拟机简介 2-6 java的垃圾回收器 2-7 java上机练习 3-1 java语言基础入门 3-2 数据的分类 3-3 标识符、关键字和常量 3-4 运算符 3-5 表达式 3-6 顺序结构和选择结构 3-7 循环语句 3-8 跳转语句 3-9 MyEclipse工具介绍 3-10 java基础知识章节练习 4-1 一维数组 4-2 数组应用 4-3 多维数组 4-4 排序算法 4-5 增强for循环 4-6 数组和排序算法章节练习 5-0 抽象和封装 5-1 面向过程的设计思想 5-2 面向对象的设计思想 5-3 抽象 5-4 封装 5-5 属性 5-6 方法的定义 5-7 this关键字 5-8 javaBean 5-9 包 package 5-10 抽象和封装章节练习 6-0 继承和多态 6-1 继承 6-2 object类 6-3 多态 6-4 访问修饰符 6-5 static修饰符 6-6 final修饰符 6-7 abstract修饰符 6-8 接口 6-9 继承和多态 章节练习 7-1 面向对象的分析与设计简介 7-2 对象模型建立 7-3 类之间的关系 7-4 软件的可维护与复用设计原则 7-5 面向对象的设计与分析 章节练习 8-1 内部类与包装器 8-2 对象包装器 8-3 装箱和拆箱 8-4 练习题 9-1 常用类介绍 9-2 StringBuffer和String Builder类 9-3 Rintime类的使用 9-4 日期类简介 9-5 java程序国际化的实现 9-6 Random类和Math类 9-7 枚举 9-8 练习题 10-1 java异常处理 10-2 认识异常 10-3 使用try和catch捕获异常 10-4 使用throw和throws引发异常 10-5 finally关键字 10-6 getMessage和printStackTrace方法 10-7 异常分类 10-8 自定义异常类 10-9 练习题 11-1 Java集合框架和泛型机制 11-2 Collection接口 11-3 Set接口实现类 11-4 List接口实现类 11-5 Map接口 11-6 Collections类 11-7 泛型概述 11-8 练习题 12-1 多线程 12-2 线程的生命周期 12-3 线程的调度和优先级 12-4 线程的同步 12-5 集合类的同步问题 12-6 用Timer类调度任务 12-7 练习题 13-1 Java IO 13-2 Java IO原理 13-3 流类的结构 13-4 文件流 13-5 缓冲流 13-6 转换流 13-7 数据流 13-8 打印流 13-9 对象流 13-10 随机存取文件流 13-11 zip文件流 13-12 练习题 14-1 图形用户界面设计 14-2 事件处理机制 14-3 AWT常用组件 14-4 swing简介 14-5 可视化开发swing组件 14-6 声音的播放和处理 14-7 2D图形的绘制 14-8 练习题 15-1 反射 15-2 使用Java反射机制 15-3 反射与动态代理 15-4 练习题 16-1 Java标注 16-2 JDK内置的基本标注类型 16-3 自定义标注类型 16-4 对标注进行标注 16-5 利用反射获取标注信息 16-6 练习题 17-1 顶目实战1-单机版五子棋游戏 17-2 总体设计 17-3 代码实现 17-4 程序的运行与发布 17-5 手动生成可执行JAR文件 17-6 练习题 18-1 Java数据库编程 18-2 JDBC类和接口 18-3 JDBC操作SQL 18-4 JDBC基本示例 18-5 JDBC应用示例 18-6 练习题 19-1 。。。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值