第 6 章 使用Spring进行面向切面编程(AOP)
[@AspectJ支持方式(注解方式)]
1、@AspectJ支持
启用该支持:
通过在你的Spring的配置中引入下列元素来启用Spring对@AspectJ的支持:
<aop:aspectj-autoproxy/>
或通过在你的application context中添加如下定义来启用@AspectJ支持:
ean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator" />
classpath中引入两个AspectJ库:aspectjweaver.jar 和 aspectjrt.jar。
2、过程
声明aspect-->声明pointCut-->声明advice
eg:声明aspect、pointcut
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class SystemArchitecture ... {
/** *//**
* A join point is in the web layer if the method is defined
* in a type in the com.xyz.someapp.web package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.someapp.web..*)")
public void inWebLayer() ...{}
/** *//**
* A join point is in the service layer if the method is defined
* in a type in the com.xyz.someapp.service package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.someapp.service..*)")
public void inServiceLayer() ...{}
/** *//**
* A join point is in the data access layer if the method is defined
* in a type in the com.xyz.someapp.dao package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.someapp.dao..*)")
public void inDataAccessLayer() ...{}
/** *//**
* A business service is the execution of any method defined on a service
* interface. This definition assumes that interfaces are placed in the
* "service" package, and that implementation types are in sub-packages.
*
* If you group service interfaces by functional area (for example,
* in packages com.xyz.someapp.abc.service and com.xyz.def.service) then
* the pointcut expression "execution(* com.xyz.someapp..service.*.*(..))"
* could be used instead.
*/
@Pointcut("execution(* com.xyz.someapp.service.*.*(..))")
public void businessService() ...{}
/** *//**
* A data access operation is the execution of any method defined on a
* dao interface. This definition assumes that interfaces are placed in the
* "dao" package, and that implementation types are in sub-packages.
*/
@Pointcut("execution(* com.xyz.someapp.dao.*.*(..))")
public void dataAccessOperation() ...{}
}
Spring AOP 支持在切入点表达式中使用如下的AspectJ切入点指定者:
-
within - 限定匹配特定类型的连接点(在使用Spring AOP的时候,在匹配的类型中定义的方法的执行)。
-
this - 限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中bean reference(Spring AOP 代理)是指定类型的实例。
-
target - 限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中目标对象(被代理的appolication object)是指定类型的实例。
-
args - 限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中参数是指定类型的实例。
-
@target
- 限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中执行的对象的类已经有指定类型的注解。 -
@args
- 限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中实际传入参数的运行时类型有指定类型的注解。 -
@within
- 限定匹配特定的连接点,其中连接点所在类型已指定注解(在使用Spring AOP的时候,所执行的方法所在类型已指定注解)。
@annotation - 限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中连接点的主题有某种给定的注解。
eg:声明adivce
2.1 前置通知(Before advice)
import org.aspectj.lang.annotation.Before;
@Aspect
public class BeforeExample ... {
@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doAccessCheck() ...{
// ...
}
}
如果使用一个in-place 的切入点表达式,我们可以把上面的例子换个写法:
import org.aspectj.lang.annotation.Before;
@Aspect
public class BeforeExample ... {
@Before("execution(* com.xyz.myapp.dao.*.*(..))")
public void doAccessCheck() ...{
// ...
}
}
2.2 返回后通知(After returning advice)
@AfterReturning
2.3 抛出后通知(After throwing advice)
@AfterThrowing
2.4 后通知(After (finally) advice)
不论一个方法是如何结束的,在它结束后(finally)后通知(After (finally) advice)都会运行。
@After
2.5 环绕通知(Around Advice)
环绕通知在一个方法执行之前和之后执行。 它使得通知有机会既在一个方法执行之前又在执行之后运行。
环绕通知使用 @Around
注解来声明。通知的第一个参数必须是 ProceedingJoinPoint
类型。 在通知体内,调用 ProceedingJoinPoint
的 proceed()
方法将会导致潜在的连接点方法执行。 proceed
方法也可能会被调用并且传入一个 Object[]
对象-该数组将作为方法执行时候的参数。
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;
@Aspect
public class AroundExample ... {
@Around("com.xyz.myapp.SystemArchitecture.businessService()")
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable ...{
// start stopwatch
Object retVal = pjp.proceed();
// stop stopwatch
return retVal;
}
}
3、通知参数(Advice parameters)
你可以在通知签名中声明所需的参数,当通知生效时可以获取所需的参数!
3.1 访问当前的连接点(即该advice生效时所处理的方法)
任何通知方法可以将第一个参数定义为 org.aspectj.lang.JoinPoint
类型 (环绕通知需要定义为 ProceedingJoinPoint
类型的, 它是 JoinPoint
的一个子类。) JoinPoint
接口提供了一系列有用的方法, 比如 getArgs()
(返回方法参数)、getThis()
(返回代理对象)、getTarget()
(返回目标)、getSignature()
(返回正在被通知的方法相关信息)和 toString()
(打印出正在被通知的方法的有用信息)。
3.2 传递参数给通知(Advice)
如果在一个参数表达式中应该使用类型名字的地方使用一个参数名字,那么当通知执行的时候对应的参数值将会被传递进来。 可能给出一个例子会更好理解。假使你想要通知(advise)接受某个Account对象作为第一个参数的DAO操作的执行,你想要在通知体内也能访问到account对象,你可以写如下的代码:
" args(account,..) " )
public void validateAccount(Account account) ... {
// ...
}
切入点表达式的 args(account,..)
部分有两个目的: 首先它保证了只会匹配那些接受至少一个参数的方法的执行,而且传入的参数必须是 Account
类型的实例, 其次它使得可以在通知体内通过 account
参数来访问那个account参数。
另外一个办法是定义一个切入点,这个切入点在匹配某个连接点的时候“提供”了一个Account
对象, 然后直接从通知中访问那个命名的切入点。你可以这样写:
" args(account,..) " )
private void accountDataAccessOperation(Account account) ... {}
@Before( " accountDataAccessOperation(account) " )
public void validateAccount(Account account) ... {
// ...
}
3.3 决定参数名
//待学
3.4 处理参数
//待学
实例:
Logger
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class Logger ... {
private static Log log = LogFactory.getLog(Logger.class);
public void entry(String message) ...{
log.info(message);
}
}
TestBean
public class TestBean ... {
public void method1() ...{
System.out.println("in method1");
}
public void method2() ...{
System.out.println("in method2");
}
}
RootTest
import junit.framework.TestCase;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public abstract class RootTest extends TestCase ... {
protected ApplicationContext ctx;
protected Log log = LogFactory.getLog(getClass());
protected RootTest() ...{
ctx = new ClassPathXmlApplicationContext(getBeanXml());
}
protected abstract String getBeanXml();
}
LogXmlTest
public class LogXmlTest extends RootTest ... {
@Override
protected String getBeanXml() ...{
return "aop2aspect.xml";
}
public void testLog() ...{
TestBean bean = (TestBean) ctx.getBean("testBean");
bean.method1();
bean.method2();
}
}
LogAspect
import net.qking.spring.aop2xml.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class LogAspect ... {
private Logger logger = new Logger();
@Pointcut("execution(public * *(..))")
public void publicMethods() ...{
}
@Pointcut("execution(* net.qking.spring.aop2xml.Logger.*(..))")
public void logObjectCalls() ...{
}
@Pointcut("publicMethods()&&!logObjectCalls()")
public void loggableCalls() ...{
}
@Around("loggableCalls()")
public Object aroundLogCalls(ProceedingJoinPoint joinPoint) throws Throwable ...{
logger.entry("before invoke method:"
+ joinPoint.getSignature().getName());
Object object = joinPoint.proceed();
logger.entry("after invoke method:"
+ joinPoint.getSignature().getName());
return object;
}
@Before("publicMethods()")
public void doCheckBefore()...{
logger.entry("in do check before");
}
@After("publicMethods()")
public void doCheckAfter()...{
logger.entry("in do check after");
}
}
配置文件
< 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-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"
>
<!-- use aspectj -->
< aop:aspectj-autoproxy />
<!-- 或者使用以下定义
<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator" />
-->
< bean id ="logAspect" class ="net.qking.spring.aop2aspect.LogAspect" />
< bean id ="testBean" class ="net.qking.spring.aop2xml.TestBean" />
</ beans >
[Schema-based AOP support]
eg:只要将上面的代码中添加下面文件,及配置文件即可。
LogBean
import org.aspectj.lang.ProceedingJoinPoint;
public class LogBean ... {
private Logger logger = new Logger();
public Object aroundLogCalls(ProceedingJoinPoint joinPoint) throws Throwable ...{
logger.entry("before invoke method:"
+ joinPoint.getSignature().getName());
Object object = joinPoint.proceed();
logger.entry("after invoke method:"
+ joinPoint.getSignature().getName());
return object;
}
}
配置文件:
< 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-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"
>
< aop:config >
<!-- expression 表示要执行的匹配表达式,这里匹配所有的public方法,但是去除logger类的所有方法,防止无限调用 -->
< aop:pointcut id ="loggableCalls"
expression ="execution(public * *(..)) and !execution(* net.qking.spring.aop2xml.Logger.*(..))" />
< aop:pointcut id ="single" expression ="execution(public void net.qking.spring.aop2xml.TestBean.method1(..))" />
< aop:aspect id ="logAspect" ref ="logBean" >
< aop:around pointcut-ref ="single"
method ="aroundLogCalls" />
</ aop:aspect >
</ aop:config >
< bean id ="logBean" class ="net.qking.spring.aop2xml.LogBean" />
< bean id ="testBean" class ="net.qking.spring.aop2xml.TestBean" />
</ beans >