Unveil Spring 连载 之 AOP应用案例

3.3. AOP应用案例(AOP Use cases)

在结束本章有关Spring AOP的话题之前,我觉得有必要从最普通的层面对适合使用AOP解决的问题场景做一简单的介绍,您愿意称其为“AOP的最佳实践 ”那也未尝不可!

3.3.1. 异常处理(Exception Handling)

或许你已经在使用类似于AOP的方式对应用程序中的某些异常进行统一的处理,也或许你已经在使用AOP的方式进行异常处理,但你可能并没有意料到这实际上对应一个很有趣的概念,叫做“Fault Barrier ”, 在接触这个名词之前,笔者实际上已经将这个概念所阐述的理念应用于工作中,直到看到dev2dev上一篇《Effective Java Exception》,我才知道,原来这种异常的处理方式,还对应一个这么“有趣的 ”术语。 不过,在进入“Fault Barrier ”之前,我们有必要先来回顾一下Java中的异常处理相关内容...

3.3.1.1. 异常处理简单攻略(Exception Handling Introduction)

关于异常的处理是一个很大的话题,所以,限于篇幅,我们不可能事无巨细的对异常处理的方方面面都提及,只是对Java中通常的异常类型和处理方式做一简单回顾,以便引出Fault Barrier的概念。

Java不是最早也不是唯一使用异常机制的语言,不过,Java却在引入“Checked Exception ”的基础上为其自身的异常处理添加了少许的新意。我们先来看一下Java中的异常层次体系大体上是一种什么样的结构,如下图:

Figure 3.24. Java异常层次体系简图

Java异常层次体系简图

在该图中,我们将java中的异常类型分为两类:
  • 通常将java.lang.Error和java.lang.RuntimeException及其子类称之为“unchecked exception ”, 之所以如此称呼,是因为编译器不会对这些类型的异常进行编译期检查,因为java.lang.Error我们通常关心不着, 所以,狭义上将java.lang.RuntimeException及其子类暂且称为“unchecked exception ”我觉得也无伤大雅吧;

  • java.lang.Exception及其子类,但除去java.lang.RuntimeException分支,统称为“checked exception ”, 一旦你在程序的方法定义中声明了将会抛出“checked exception ”,调用程序便必须对这些异常进行处理,而且编译器会在编译期间对这些异常类型进行检查;

各位大师级人物对“checked exception ”和“unchecked exception ”的论战我们暂且放在一边, 不过,对于二者具体的应用场景我们有必要了解一下,毕竟基本原则还是有的,至于用好用不好那就由不得人了:

  • unchecked exception ”通常对应系统中的严重异常情况,这些情况应用程序通常无法恢复,比如数据库挂掉,网线连接中断,服务器崩溃等等。 所以,“unchecked exception ”所提供的信息通常不是为应用程序准备的,而是为人准备的,确切的说,是为了能够让系统维护人员能够根据所提供的信息来判定到底哪里出了问题,以便人工干预;

  • checked exception ”引入java后,一直是备受争议,不过,我觉得这跟概念本身没有任何关系,是否在应用于合适的场合是由人来决定的,而不是概念本身。 “checked exception ”通常用于表明系统中的某些罕见的非正常状态,比如,对于一个业务方法来说,使用“错误号(Error Code) ”的时代我们是通过返回-1之类数字表明一些非正常状态, 现在,我们可以通过抛出不同类型的“checked exception ”来表明这些非正常状态,并要求调用方对这些非正常状态进行处理,而编译器对“checked exception ”的检查可以进一步加强这种契约关系。 “checked exception ”通常是可恢复的,也是意料之中的,它所提供的信息是面向应用程序,而不是人,应用程序对不同的“checked exception ”类型可以根据系统逻辑有针对性的进行处理;

在《Effective Java Exception》中,作者将“unchecked exception ”对应的情况称之为“Fault ”,而将“checked exception ”对应的情况称之为“Contingency ”. 而我们的“Fault Barrier ”要处理的,就是对应Fault的情况。

3.3.1.2. fault barrier

对于“checked exception ”来说,不同的类型可以有不同的处理方式,这完全是由系统逻辑来决定的,调用方可以根据不同的类型,有针对性的对“checked exception ”进行处理; 反过来,对于“unchecked exception ”来说,不同的类型则是没有太多必要的,因为不管你的应用程序抛出何种类型的“unchecked exception ”, 最终都是需要人来进行干预,只要“unchecked exception ”能够提供足够的信息,相应人员就可以进行处理,完全就是无差别对待。

当系统中多个地方都可能抛出“unchecked exception ”的时候,在此之前,我们可能会在每一个调用的最顶层分别添加异常处理逻辑对其进行处理,而就像我们所说的那样, 对于“unchecked exception ”来说,实际上可以做的事情很少,通常就是记录日志,通知相应人员,所以,这些相同的逻辑实现实际上应该归并于一处进行处理,而不是让他们散落到系统的各处,也就是说, 对于系统中的Fault来说,它实际上就是一种横切关注点(Cross-cutting concern)。

鉴于此,我们完全可以实现一个对应Fault处理的Aspect,让其对系统中的所有可能的Fault情况进行统一的处理,那么,这个专职于处理Fault的Aspect,我们就可以称之为“Fault Barrier ”。

实际上,我们从讲解SpringAOP的ThrowsAdvice开始,就提供了一个“Fault Barrier ”的实现实例,在该实例中,我们通过email方式将系统中的Fault情况,也就是以“unchecked exception ”形式给出的信息转发给相关人员,并记录到日志。 当然,如果可能,你还可以加入更多的处理,比如分析“unchecked exception ”信息,为相关人员提供更加友好的系统信息等等。

Note

任何的概念都会有特例,对于异常来说也是如此,比如“unchecked exception ”也可以被相应的方法声明并被调用方捕获,不过,前面也说了,篇幅有限,无法完全囊括所有内容, 对于特殊的情况,如果后继内容有所牵扯,我们会给予相应解释。

3.3.2. 安全检查(security checking)

如果你已经Java开发web应用程序多年,那么,你一定不会对使用Filter为系统的资源访问添加控制而感到陌生吧? javax.servlet.Filter可以算是servlet规范为我们提供的一种AOP支持,通过它,你可以为基于servlet的web应用添加相应的资源访问控制(当然,还可以做很多其他事情)。 不过,基于Filter的web应用的资源访问控制仅仅是特定领域的安全检查需求,实际上,通过AOP,你可以为任何类型的应用添加相应的安全支持。

在介绍AOP概念的时候我们就曾经提到过,安全检查属于系统的一种横切关注点,按照原先的方法进行系统开发,势必让这些安全检查逻辑散落系统各处, 所以,对付它的最好办法就是用AOP。在将系统中可能需要安全检查的点排查清楚之后,我们就可以为这些点织入安全检查的逻辑了。

要为系统中某个点添加安全支持,最简单的办法就是提供一个Interceptor,对所有访问该点的调用进行拦截。所以,对于基本的一个安全检查的Aspect实现来说,基本类似于:

@Aspect
public class SecurityAspect
{
	@Around("...")
	public Object doCheck(ProceedingJoinPoint pjp) throws Throwable
	{
		if(isIllegalRequest(pjp))
		{
			throw new SecurityCheckingException("necessary information");
		}
		return pjp.proceed();
	}
}
				
不过,既然我们崇尚“ 不重新发明轮子 ”, 在动手之前,有必要google一下是否有现成的,也好免去人力物力的浪费啊。

实际上,作为基于Spring平台的一套安全框架,Acegi framework现在可以说在企业级应用的安全领域声名远扬了。 它在Spring基础之上,提供了完备的系统认证,授权,访问控制等安全检查功能。Acegi Framework最初是独立于Spring开发的,现在已经并入Spring portfolio,更名为Spring Security, 你可以在http://www.acegisecurity.org/获得有关Acegi的更多信息。

3.3.3. 缓存(caching)

AOP应用的另一个主要场景在于为系统透明的添加缓存支持。缓存可以在很大程度上提供系统的性能,但它不属于业务需求, 而是系统需求,在现有方法论的基础之上要为系统添加缓存支持,就会因为系统中缓存需求的广泛分布,造成实现上的代码散落。

为了避免需要添加的缓存的实现逻辑污染业务逻辑的实现,我们可以让缓存的实现独立于业务对象的实现之外,将系统中的缓存需求通过AOP的Aspect进行封装, 只有当系统中某个点确切需要缓存支持的情况下,才为其织入。

使用AOP为系统添加缓存其实很简单,比如:

@Aspect
public class CachingAspect
{
	private static Map cache = new LRUMap(5);
	
	@Around("...")
	public Object doCache(ProceedingJoinPoint pjp,Object key) throws Throwable
	{
		if(cache.containsKey(key))
		{
			return cache.get(key);
		}
		else
		{
			Object retValue = pjp.proceed();
			cache.put(key, retValue);
			return retValue;
		}
	}
}
				
我想,在没有使用AOP之前,要为系统某个地方加入缓存的话,你也是以差不多的逻辑实现的。

不过,现在我们实际上不需要这么做了:

  1. 现在已经有许多现成的Caching产品实现,包括EhCache,JBossCache等等;

  2. Spring Extensions (Modules)项目提供了对现有Caching产品的集成,现在你可以通过外部声明的方式为系统中的Joinpoint添加Caching支持。

你可以从https://springmodules.dev.java.net/获得更多有关Spring Extensions (Modules)的信息,另外,dev2dev网站一篇 《Declarative Caching Services for Spring》 (http://dev2dev.bea.com/pub/a/2006/05/declarative-caching.html)专门介绍了如何通过Spring Cache为你的应用程序添加声明性的Caching支持。

Note

更多AOP的应用场景和最佳实践还需要您自己去挖掘,去探索...

你可能感兴趣的:(spring,AOP,应用服务器,企业应用,Acegi)