Spring的定义
对DI的初步理解
对AOP的初步理解
对DI的深入探究
对AOP的深入探究
Spring的事务管理
Spring MVC
下面我将论述一下Spring的AOP。首先,介绍一下AOP的作用:把交叉事务(散布在程序中多个地点的函数)和业务逻辑代码分离开,同时,有些事情也是要被动执行的,比如经典的登录,当用户没有登录的时候,系统会提示登录,而不是在展示页面的时候程序主动的去判断用户有没有登录。这也正是Spring的好处之一。像登录这种代码在系统中是会被用到多次的,代码重用的主要面向对象技术就是委托和继承。而反复继承同一个基类可能会形成很脆弱的对象关系。切面正是这样的一种取代二者的方式。
接下来我介绍几个AOP的术语,这些术语很重要。
通知:通知定义了切面要完成的工作、以及何时执行这个工作。
连接点:连接点是在程序的执行过程中能够插入切面的一个点,比如方法被调用时、异常抛出时等等。
切入点:一个切面不是要通知程序里所有的连接点的。切入点缩小了切面通知连接点的范围。
切面:通知和切入点的结合。
目标:就是被通知的对象。
接下来我来创建一个典型的spring切面。因此,首先引入一个场景:观众观看演奏者表演。所以,先定义一个观众类:
public class Audience
{
//观众观看表演之前就座
public void takeSeats()
{}
//观看表演之前关闭手机
public void turnOffCellPhones()
{}
//表演之后,如果表演成功就鼓掌
public void applaud()
{}
//观看之后,如果表演不好就要求退票
public void demandRefund()
{}
}
接下来在xml文件中加入如下定义:
<bean id="audience" class="com.springinaction.springidol.Audience" />
这样,场景就描述完了。当表演者表演的时候,可以先调用观众就座和关闭手机的方法,然后演奏者执行表演方法,如果中途发生异常,就执行观众退票,如果成功就鼓掌。
当然这是可行的,但是仔细一想会发现多少有点别扭。这不仅将观众类耦合到表演者类里面,而且表演者在表演的过程中,还要举着牌子,负责提示观众关手机,或者鼓掌。因此,这种设计明显是不合理的。
下面我们就用AOP来改造这个场景,让它变得合理。首先,介绍一下spring的aop中的5种通知类型:执行前通知、返回后通知、抛出后通知、周围通知、引入通知。在这个场景里,会用到前三个通知。就座和关闭手机应该是执行前通知,鼓掌应该是返回后通知,退票应该是抛出后通知。这段通知的代码如下(该通知依赖于观众类):
public class AudienceAdvice implements MethodBeforeAdvice, AfterReturningAdvice, ThrowsAdvice
{
//执行前通知
public void before(Method method, Object[] args, Object target) throws Throwable
{
audience.takeSeats(); //就座
audience.turnOffCellPhones(); //关闭手机
}
//返回后通知
public void afterReturning(Object rtn, Method method, Object[] args, Object target)
{
audience.applaud(); //鼓掌
}
//抛出后通知
public void afterThrowing(Throwable throwable)
{
audience.demandRefund(); //退票
}
//观众类
private Audience audience;
public void setAudience(Audience audience)
{
this.audience = audience;
}
}
上面类中的每一个方法都是实现三个通知接口之后要求实现的方法。这没什么好说的。只是大家注意一下方法的参数即可。
下面我们以周围通知的方式来实现一下这个场景:
public class AudienceAroundAdvice implements MethodInterceptor
{
public Object invoke(MethodInvocation invocation) throws Throwable
{
try {
audience.takeSeats(); //就座
audience.turnOffCellPhones(); //关闭手机
Object returnValue = invocation.proceed(); //调用目标方法
audience.applaud(); //鼓掌
return returnValue;
}
catch (PerformanceException throwable) {
audience.demandRefund(); //退票
throw throwable;
}
}
//观众类
private Audience audience;
public void setAudience(Audience audience) {
this.audience = audience;
}
}
周围通知的一个最大的优势就是能够在一个方法中定义前通知和后通知,而且还很简洁。现在我们已经了解了好几种创建通知的方法了,但是还没有提到如何创建切点,没有切点,也就没有切面了。下面我们就来说说切点,spring提供了多种类型的切点。两种最常用的就是正则表达式切点和AspectJ表达式切点。首先来看正则表达式切点。
还记得前面提到的表演者的类吗?每个表演者都实现了一个perform方法。现在我们要做的就是,在每个表演者表演的时候(执行perform方法时),插入上述定义的观众的行为。
切点的定义如下:该正则表达式匹配所有类中含有名为“perform”的方法。
<bean id="audiencePointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut"> <property name="pattern" value=".*perform" /> </bean>
然后,我们需要把上述切点与通知相关联,定义如下bean:
<bean id="audienceAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"> <property name="advice" ref="audienceAdvice" /> <property name="pointcut" ref="audiencePointcut" /> </bean>
这样,一个完整的切面就这样被定义了。但是在spring中,切面是以代理的方式实现的。无论使用哪种方式定义切点,都是需要代理bean的。这些东西我们下面再说,现在,先介绍一下如何定义AspectJ切点:
<bean id="audienceAdvisor" class="org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor"> <property name="advice" ref="audienceAdvice" /> <property name="expression" value="execution(* *.perform(..))" /> </bean>
execution(* *.perform(..)):
execution:执行方法时;
第一个星号:任何返回类型;
第二个星号:任意类;
perform :代表perform方法;
(..) :任意参数设置。
现在,我们就来说一些有关代理目标bean的东西。现在,我们来为一个名为Duke的诗人表演者定义一个代理bean:
原Duke bean的定义:
<bean id="dukeTarget" class="com.springinaction.springidol.PoeticJuggler" autowire="constructor"> <constructor-arg ref="sonnet29" /> </bean> Duke bean的代理的定义: <bean id="duke" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target" ref=" dukeTarget " /> <property name="interceptorNames" value=" audienceAdvisor " /> <property name="proxyInterfaces" value ="com.alibaba.performer" /> </bean>
interceptorNames属性指明哪个通知者要应用于被代理的bean。
proxyInterfaces属性指明代理应该实现哪个接口。
上述的两个属性其实都是String的数组。在该例中,spring会将单个值转换成数组的。如果有多个值可以这样做:
<property name="interceptorNames" > <list> <value> audienceAdvisor</value> </list> </property>
当然,如果现在又有一个叫steve的人来表演萨克斯的话,我们就会发现,上述代码还要重写一遍,而其中只有target属性不同。这样,我们就需要抽象ProxyFactoryBean:
<bean id="audienceProxyBase" class="org.springframework.aop.framework.ProxyFactoryBean" abstract=”true”> <property name="proxyInterfaces" value="com.alibaba.Performer" /> <property name="interceptorNames" value="audienceAdvisor" /> </bean> <bean id="duck" parent=" audienceProxyBase "> <property name="target" ref="duckTarget" /> </bean>
这样,我们就介绍完了spring最经典的基于代理方式实现的aop。下面我们来看一下@AspectJ注解驱动的切面。我们仍以那个观众观看表演的情景来做例子。
@Aspect //该类已经不是一个传统的POJO了,而是一个切面
public class Audience
{
@Pointcut("execution(* *.performance(..))") //定义一个切面
public void performance(){}
@Before("performance()") //前通知
public void takeSeats() {
System.out.println("The audience is taking their seats.");
}
@Before("performance()")
public void turnOffCellPhones() {
System.out.println("The audience is turning off their cellphones");
}
@AfterReturn("performance()") //返回后通知
public void applaud() {
System.out.println("CLAP CLAP CLAP CLAP CLAP");
}
@AfterThrowing("performance()") //抛出后通知
public void demandRefund() {
System.out.println("Boo! We want our money back!");
}
}
当然,不要忘了在xml配置文件里加上<aop:aspectj-autoproxy/>,这个标识是告诉spring去扫描被@Aspect标记的切面了。周围通知的写法也很类似,@Around("performance()")即可。上面的代码没什么好说的,读起来很简单,就像读报纸一样,这里就不废话了。下面说说另一种切面:纯粹的POJO切面。这种切面更加直观,他可以将任何一个java类变成一个切面。就拿我们最初的那个观众类举例,他就是一个很普普通通的类。但是通过如下的xml配置之后,这个观众类就是一个切面了,但是原来的java类没有任何代码上的改变。
<bean id="audience" class="com.springinaction.springidol.Audience" /> <aop:config> <aop:aspect ref="audience"> <aop:pointcut id="performance" expression="execution(* *.perform(..))" /> <aop:before method="takeSeats" pointcut-ref="performance" /> <aop:before method="turnOffCellPhones" pointcut-ref="performance" /> <aop:after-returning method="applaud" pointcut-ref="performance" /> <aop:after-throwing method="demandRefund" pointcut-ref="performance" /> </aop:aspect> </aop:config>