AspectJ切入点@Pointcut语法详解非常详细
分类pointcuts 遵循特定的语法用于捕获每一个种类的可使用连接点。 主要的种类:
方法执行:execution(MethodSignature)
方法调用:call(MethodSignature)
构造器执行:execution(ConstructorSignature)
构造器调用:call(ConstructorSignature)
类初始化:staticinitialization(TypeSignature)
属性读操作:get(FieldSignature)
属性写操作:set(FieldSignature)
例外处理执行:handler(TypeSignature)
对象初始化:initialization(ConstructorSignature)
对象预先初始化:preinitialization(ConstructorSignature)
Advice执行:adviceexecution()
切入点指示符用来指示切入点表达式目的,,在Spring AOP中目前只有执行方法这一个连接点,Spring AOP支持的AspectJ切入点指示符如下:
execution:用于匹配方法执行的连接点;
within:用于匹配指定类型内的方法执行;
this:用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配;
target:用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;
args:用于匹配当前执行的方法传入的参数为指定类型的执行方法;
@within:用于匹配所以持有指定注解类型内的方法;
@target:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解;
@args:用于匹配当前执行的方法传入的参数持有指定注解的执行;
@annotation:用于匹配当前执行方法持有指定注解的方法;
bean:Spring AOP扩展的,AspectJ没有对于指示符,用于匹配特定名称的Bean对象的执行方法;
reference pointcut:表示引用其他命名切入点,只有@ApectJ风格支持,Schema风格不支持。
来了解下AspectJ类型匹配的通配符:
:匹配任何数量字符;
…:匹配任何数量字符的重复,如在类型模式中匹配任何数量子包;而在方法参数模式中匹配任何数量参数。
+:匹配指定类型的子类型;仅能作为后缀放在类型模式后边。
java.lang.String 匹配String类型;
java..String 匹配java包下的任何“一级子包”下的String类型;
如匹配java.lang.String,但不匹配java.lang.ss.String
java…* 匹配java包及任何子包下的任何类型;
如匹配java.lang.String、java.lang.annotation.Annotation
java.lang.*ing 匹配任何java.lang包下的以ing结尾的类型;
java.lang.Number+ 匹配java.lang包下的任何Number的自类型;
如匹配java.lang.Integer,也匹配java.math.BigInteger
举例说明:
任意公共方法的执行:
execution(public * (…))
任何一个以“set”开始的方法的执行:
execution( set*(…))
AccountService 接口的任意方法的执行:
execution(* com.xyz.service.AccountService.(…))
定义在service包里的任意方法的执行:
execution( com.xyz.service..(…))
定义在service包和所有子包里的任意类的任意方法的执行:
execution(* com.xyz.service….(…))
定义在pointcutexp包和所有子包里的JoinPointObjP2类的任意方法的执行:
execution(* com.test.spring.aop.pointcutexp…JoinPointObjP2.*(…))")
*> 最靠近(…)的为方法名,靠近.(…))的为类名或者接口名,如上例的JoinPointObjP2.(…))
pointcutexp包里的任意类.
within(com.test.spring.aop.pointcutexp.)
pointcutexp包和所有子包里的任意类.
within(com.test.spring.aop.pointcutexp…)
实现了Intf接口的所有类,如果Intf不是接口,限定Intf单个类.
this(com.test.spring.aop.pointcutexp.Intf)
***> 当一个实现了接口的类被AOP的时候,用getBean方法必须cast为接口类型,不能为该类的类型.
带有@Transactional标注的所有类的任意方法.
@within(org.springframework.transaction.annotation.Transactional)
@target(org.springframework.transaction.annotation.Transactional)
带有@Transactional标注的任意方法.
@annotation(org.springframework.transaction.annotation.Transactional)
***> @within和@target针对类的注解,@annotation是针对方法的注解
参数带有@Transactional标注的方法.
@args(org.springframework.transaction.annotation.Transactional)
参数为String类型(运行是决定)的方法.
// @Aspect不再能修饰接口,而只能是类
// 访问aspect实例时,不再能使用aspectOf()和hasAspect()
// 而应以aspect的类作为参数,使用由org.aspectj.lang.Aspects提供的静态方法aspectOf()与hasAspect()
@Aspect(“perthis|pertarget|percflow|percflowbelow(Pointcut) | pertypewithin(TypePattern)”)
// 定义aspect的优先顺序,需要使用完全的限定名,这在@AspectJ中很普遍,也是由Java编译器决定的
// AspectJ的未来版本可能提供string[]类型的参数支持
@DeclarePrecedence(“ajia.HomeSecurityAspect, ajia.SaveEnergyAspect”)
public abstract static class AspectName
extends class_or_aspect_name
implements interface_list
{
// 使用@Pointcut配合一个占位用的方法声明来定义一个pointcut
// 抽象pointcut依旧只有名字、参数,没有实际的joinpoint定义
@Pointcut
public abstract void pointcut_name(Type args);
// pointcut定义时仍要注意使用全限定名
// 方法只是占位符,方法体除了采用类似条件编译时的if()切入方式外都置空
// 若方法会抛出异常,则同样要在方法原型加上throws声明
// 切记要开启编译器选项-g:vars,让编译器预留参数名(建设采用这种方式)
@Pointcut("execution(public * ajia.banking.domain.Account.*(float)) && this(account) && args(amount)")
public void accountOperation(Account account, float amount) {}
// 或者利用Annotation的属性,建立参数名与pointcut之间的关联
// 但这样得自己维护argNames与方法参数表的一致性,所以不推荐
@Pointcut(value="execution(public * ajia.banking.domain.Account.*(float)) && this(account) && args(amount)",
argNames="account, amount")
public void accountOperation(Account account, float amount) {}
// advice的定义类似传统语法,
// before-advice必须是public与void的
// 方式一:匿名pointcut
@Before("execution(* *(..)) && !within(ajia.monitoring.*)")
public void beatHeart()
{
heartBeatListener.beat();
}
// 方式二:命名的pointcut
@Pointcut("execution(* *.*(..)) && !within(ajia.monitoring.*)")
public void aliveOperation() {}
@Before("aliveOperation()")
public void beatHeart()
{
heartBeatListener.beat();
}
// advice仍旧支持经由类JoinPoint的反射获取上下文
// JoinPoint对象本身定义动态部分
// JoinPoint.StaticPart定义静态部分
// JoinPoint.EnclosingStaticpart定义包裹静态信息的部分
// 同时,advice仍旧支持target/this/args
@Pointcut("call(void Account.credit(float)) && target(account) && args(amount)")
public void creditOperation(Account account, float amount) {}
@Before("creditOperation(account, amount)" )
public void beforeCreditOperation(JoinPoint.StaticPart jpsp, JoinPoint.EnclosingStaticPart jpesp,
Account account, float amount)
{
System.out.println("Crediting " + amount + " to " + account);
}
// after-advice的实现同样直观
@Pointcut("call(* java.sql.Connection.*(..)) && target(connection)")
public void connectionOperation(Connection connection) {}
@After("connectionOperation(connection)")
public void monitorUse(Connection connection)
{
System.out.println("Just used " + connection);
}
@AfterReturning(value="connectionOperation(connection)", returning="ret")
public void monitorSuccessfulUse(Connection connection, Object ret)
{
System.out.println("Just used " + connection + " successfully which returned " + ret);
}
@AfterThrowing(value="connectionOperation(connection)", throwing="ex")
public void monitorFailedUse(Connection connection, Exception ex)
{
System.out.println("Just used " + connection + " but met with a failure of kind " + ex);
}
// around-advice的实现稍显复杂
// 需要参考JoinPoint反射的方式,为around-advice的方法传入一个ProceedingJoinPoint参数
// 该对象有方法proceed()及其重载版本proceed(Object[]),可以执行被切入的方法
// 这个Object[]数组中,依次为this-target-args
// 分别经由ProceedingJoinPoint对象的方法this()、target()与getArgs()获取
@Around("pointcut_xxx()")
public Object measureTime(ProceedingJoinPoint pjp)
{
Object[] context = formProceedArguments(pjp.this(), pjp.target(), pjp.getArgs());
Object result = proceed(context);
return result;
}
// 可以用下面这个方法获取该Object[]数组
public static Object[] formProceedArguments(Object thiz, Object target, Object[] arguments)
{
int argumentsOffset = 0;
if(thiz != null) { argumentsOffset++; }
if(target != null) { argumentsOffset++; }
Object[] jpContext = new Object[arguments.length + argumentsOffset];
int currentIndex = 0;
if(thiz != null) { jpContext[currentIndex++] = thiz; }
if(target != null) { jpContext[currentIndex++] = target; }
System.arraycopy(arguments, 0,jpContext, argumentsOffset, arguments.length);
return jpContext;
}
// 声明Error与Warning
@DeclareError("callToUnsafeCode()")
static final String unsafeCodeUsageError =
"This third-party code is known to result in a crash";
@DeclareWarning("callToBlockingOperations()")
static final String blockingCallFromAWTWarning =
"Please ensure you are not calling this from the AWT thread";
// @AspectJ提供了@DeclareParents,但很少使用,而更多使用下面的@DeclareMixin作为替代
// @AspectJ实现的Mix-in,本质是一个返回proxy对象的工厂方法,用于返回一个包裹了aspect的proxy
// 下面的代码等价于:declare parents: ajia.banking.domain.* implements Serializable;
// 由于返回null,因此只能作为对被切入类的都有Serializable的一个标记
@DeclareMixin("ajia.banking.domain.*")
public Serializable serializableMixin()
{
return null;
}
// @DeclareMixin支持一个参数,模仿依赖注入的方式,把被切入的对象传递给工厂
// AuditorImp是接口Auditor的一个实现,即对Object的一个代理
@DeclareMixin("ajia.banking.domain.*")
public Auditor auditorMixin(Object mixedIn)
{
return new AuditorImpl(mixedIn);
}
// 要mix-in若干个接口,则需要在interfaces里依次放上要添附的接口
@DeclareMixin(value="ajia.banking.domain.*", interfaces="{Auditor.class, MonitoringAgent.class}")
public AuditorMonitoringAgent mixin()
{
return new AuditorMonitoringAgentImpl();
}
}.
/**
* 定义一个切入点 只拦截controller.
* 解释下:
* ~ 第一个 * 代表任意修饰符及任意返回值.
* ~ 第二个 * 定义在web包或者子包
* ~ 第三个 * 任意方法
* ~ … 匹配任意数量的参数.
*/
@Pointcut("execution(* com.precinct.data.controller..*.*(..))")
public void logPointcut() {
}
//around(和上面的方法名一样)
@org.aspectj.lang.annotation.Around("logPointcut()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
LOG.info("=====================================Method start====================================");
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
long start = System.currentTimeMillis();
String requestMethod = request.getMethod();
try {
Object result = joinPoint.proceed();
long end = System.currentTimeMillis();
LOG.info("请求地址:" + request.getRequestURI());
LOG.info("用户IP:" + request.getRemoteAddr());
LOG.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
if ("POST".equals(requestMethod)){
LOG.info("POST请求参数: " + getRequest(request));
}else if ("GET".equals(requestMethod)){
LOG.info("GET请求参数: " + URLDecoder.decode(request.getQueryString(), "UTF-8" ));
}
LOG.info("执行时间: " + (end - start) + " ms!");
LOG.info("=====================================Method End====================================");
return result;
} catch (Throwable e) {
long end = System.currentTimeMillis();
LOG.info("URL:" + request.getRequestURI());
LOG.info("IP:" + request.getRemoteAddr());
LOG.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
if ("POST".equals(requestMethod)){
LOG.info("POST请求参数: " + getRequest(request));
}else if ("GET".equals(requestMethod)){
LOG.info("GET请求参数: " + URLDecoder.decode(request.getQueryString(), "UTF-8" ));
}
LOG.info("执行时间: " + (end - start) + " ms!");
LOG.info("=====================================Method End====================================");
String requestParam = request.getQueryString();
System.out.println("requestParam" + requestParam);
System.out.println("requestParam" + requestParam);
throw e;
}
}
public static String getRequest(HttpServletRequest request){
StringBuffer sb = new StringBuffer();
BufferedReader reader = null;
String inputParam = "";
try {
reader = request.getReader();
String line = "";
while((line = reader.readLine()) != null) {
sb.append(line);
}
inputParam = URLEncoder.encode(sb.toString(), HTTP.UTF_8);
inputParam = URLDecoder.decode(inputParam, HTTP.UTF_8);
} catch (Exception e) {
inputParam = "";
}
return inputParam;
}