前提
这篇博文是这套Spring学习笔记的第七篇——AOP篇(下),主要内容包含AspectJ的进阶知识和基于配置文件的AOP配置。如果需要了解有关Spring的综述信息或博文的索引信息,请移步:
《综述篇》
为了缩减不必要的篇幅,现将下文中将会出现的示例代码缩减为:
@Aspect
public class BeforeHandleRequestAspect{
@Before("execution(* com.implementist.MyFirstWebApp.controller.*(..))")
public void beforeHandleRequest(JoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
HttpServletRequest request = (HttpServletRequest) args[0];
request.setCharacterEncoding("utf-8");
}
}
具名切点
前面,我们用切点表达式函数定义的切点都是一次性的,当即定义当即使用,他们都是匿名切点。有的时候,我们可能需要复用某些切点,因此必须给切点赋上一个名字,使它成为具名切点。
我们先定义一个类来存储切点
public class NamedPointcut{
@Pointcut("within(com.implementist.MyFirstWebApp.controller)") //注释①
public void controllerPointcut(){} //注释②
}
注释:
①通过@Pointcut
注解定义切点表达式,该切点表达式匹配
com.implementist.MyFirstWebApp.controller
包下的所有类的所有函数;
②controllerPointcut
是具名切点的名字,方法体为空。
然后在切面类中引用上述切点
@Aspect
public class ControllerAspect{
@Before("NamedPointcut.controllerPointcut()") //注释
public void beforeHandleRequest(){
......
}
}
注释:这里引用了
NamedPointcut
类中定义的controllerPointcut
,注意要加括号。
访问连接点信息
前面我们所用在示例中编写的增强函数都是没有参数的,当我们需要访问切点的相关信息时,可以直接给括号里定义一个切点类的参数,然后通过该变量进行访问。关于传值的问题,不用我们操心,Spring容器会自动注入相应的bean到这个参数引用中。
JoinPoint
一般情况下使用JoinPoint就可以了。
比如我们前面实现对处理请求的函数织入前置增强的代码:
@Aspect
public class BeforeHandleRequestAspect{
@Before("execution(* com.implementist.MyFirstWebApp.controller.*(..))")
public void beforeHandleRequest(JoinPoint joinPoint){ //注释①
Object[] args = joinPoint.getArgs(); //注释②
HttpServletRequest request = (HttpServletRequest) args[0]; //注释③
request.setCharacterEncoding("utf-8");
}
}
注释:
①定义JoinPoint
类型的参数;
②通过joint.getArgs()
函数获取参数数组。注意!这里的参数是只读的,经过测试,这里获取到的参数被修改后,目标函数的入参不会变(上述示例中出现了例外,可能是设置字符编码的三个函数在实现中实际改变的是服务器容器的全局变量)。即这里获取的参数列表是一个Copy;
③通过强制类型转换得到具体的参数。
ProceedingJoinPoint
他继承自JoinPoint,@Around
环绕增强中必须使用ProceedingJoinPoint,它较JoinPoint多两个用于执行切点函数的函数proceed()
和proceed(Object[] args)
。为什么要增加执行切点函数的函数呢?因为环绕增强是要在切点的两端执行操作。我在刚开始接触环绕增强这个概念时,理解的一个误区是:我写好了增强函数体,然后环绕增强在函数两端都调用它一下。
其实不然,环绕增强远比这灵活得多,你可以在切点两端执行不同的操作,因此,就需要proceed()这个函数来指示切点函数在哪个位置执行。
@Aspect
public class AroundHandleRequestAspect{
@Around("execution(* com.implementist.MyFirstWebApp.controller.*(..))")
public void aroundHandleRequest(ProceedJoinPoint proceedJoinPoint){
Object[] args = joinPoint.getArgs();
HttpServletRequest request = (HttpServletRequest) args[0];
request.setCharacterEncoding("utf-8");
proceedJoinPoint.proceed(); //注释
Logger.getLogger(AroundHandleRequestAspect.class.getName(), null).log("Around Handle Request Executed!");
}
}
注释:可以看到,我把切点函数的执行位置放到了倒数第二行,最后一行我加了一条日志记录的操作。
更重要的还要数带参数的proceed(Object[] args)
这个函数,他可以解决getArgs()
函数获取的参数列表只读的问题,我们可以在获取了参数列表后,通过proceed(Object[] args)
用改变之后的参数列表来调用切点函数。
@Aspect
public class AroundFunction{
@Around("execution(* com.implementist.MyFirstWebApp..*.*Function())")
public void around(ProceedJoinPoint proceedJoinPoint){
Object[] args = proceedJoinPoint.getArgs();
int arg0 = (int) args[0];
arg0 *= 4;
args[0] = arg0;
proceedJoinPoint.proceed(args); //注释
}
}
注释:在这个增强方法体中,我们获取到切点函数的第一个参数后,做了一些改变,又把改变后的新参数填回参数列表中,用新的参数列表去调用切点函数。
绑定切点函数的参数
前面的代码中我们都需要先把参数列表取下来,然后再根据参数在切点函数中声明的顺序依次获取并强制类型转换才能使用。如果参数列表过长,会额外多出来很多行用于转换的代码。我们可以通过args()
函数来“按名”绑定需要在增强中操作的参数,这样就不用再手动转换了,Spring容器会自动为我们注入这些参数。
@Aspect
public class BeforeHandleRequestAspect{
@Before("execution(* com.implementist.MyFirstWebApp.controller.*(..)) && args(request)") //注释①
public void beforeHandleRequest(HttpServletRequest request){ //注释②
request.setCharacterEncoding("utf-8"); //注释③
}
}
注释:
①可以看到,我们将execution()
和args()
函数进行了与操作,在使用args()
进一步精确匹配仅有一个名为request
的参数的函数;
②这里需要根据绑定的参数的实际类型和绑定的参数名来定义形参;
③这里就不需要手动获取参数了,直接使用即可。
绑定代理对象和绑定注解对象的方式与绑定参数的方法类似,按不同的需要更改①处的切点表达式函数和②处的参数类型和名称即可。
绑定返回值
使用后置增强可以绑定函数的返回值
@AfterReturning(value="within(com.implementist.MyFirstWebApp.Utils)",returning="result") //注释①
public void returningUtilsInvoked(String result){ //注释②
......
}
注释:
①可以看到后置增强的参数和之前的不同,之前的切点表达式被赋予value
参数,后面多出来一个returning
参数。但仅使用切点表达式时,直接写在括号里和把字符串赋给value
是等价的。returning参数按名定义了要绑定的返回值;
②与绑定参数类似,这里的参数名要与①处相同。
绑定异常的方法与绑定返回值类似,将①处的returning替换为throwing即可。
基于配置文件的AOP配置
Spring AOP提供了方便的基于配置文件的AOP配置,使我们可以把大部分AOP配置放到配置文件中,Java文件中仅需要留下少量代码即可,如增强的函数体等。
首先,我们在com.implementist.MyFirstWebApp
包下创建一个AdviceMethods
类,用来存放增强函数。
public class AdviceMethods {
public void setEncodingBeforeHandlingRequest(HttpServletRequest request, HttpServletResponse response) {
try {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
response.setCharacterEncoding("utf-8");
} catch (UnsupportedEncodingException ex) {
Logger.getLogger(AdviceMethods.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
接下来在spring-mvc.xml
中配置AOP
<bean id="adviceMethods" class="com.implementist.MyFirstWebApp.AdviceMethods"/>
<aop:config proxy-target-class="true">
<aop:pointcut id="beforeHandlingRequest" expression="within(com.implementist.MyFirstWebApp.controller.*) and args(request,response)"/>
<aop:aspect ref="adviceMethods">
<aop:before method="setEncodingBeforeHandlingRequest" pointcut-ref="beforeHandlingRequest"/>
</aop:aspect>
</aop:config>
注释:
①我们首先定义一个AdviceMethods
类的bean
——adviceMethods
;
②其次定义<aop:config>
——AOP 配置标签,设置proxy-target-class=true
,即启用基于类的代理;
③定义具名切点beforeHandleRequest
;
④定义切面,引用adviceMethods
;
⑤在切面中定义前置增强,指定增强函数体,并引用具名切点beforeHandlingRequest
。
后记
AOP知识点很多,难理解的地方不多,实在理解不了的地方可以写写代码,做做测试,然后分析结果就能加深理解。