一、前置通知
如何声明前置通知:在切面的的一个方法上面使用@Before("切入点表达式")声明。
前置通知何时执行:在目标方法执行之前执行。目标方法:切入点表达式中指定的方法。
AspectJ 切入点表达式的编写:
execution(* com.sqp.spring.aop.dao.MyCalculator.*(..))
第一个 *表示任意修饰符任意返回值。
第二个 *表示 com.sqp.spring.aop.dao.MyCalculator类中任意方法。
(..)表示匹配任意数量的参数。
可以在通知方法中声明一个JoinPoint 类型的参数,然后就可以访问连接细节,如方法名,参数。
举个例子:
/**
* @Component: 把该放入Spring IOC容器中
* @Aspect: 声明该类为一个切面(需要先放入Spring IOC容器中)
*/
@Aspect
@Component
public class loggingAspect
{
/**
* 前置通知: 在目标方法执行之前执行前置通知(使用execution()指定目标方法)
*/
@Before("execution(* com.sqp.spring.aop.dao.MyCalculator.*(..))")
public void beforeAdvice(JoinPoint joinPoint)
{
//获取方法名
String methodName = joinPoint.getSignature().getName();
//获取入参
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("The method [" + methodName + "] input params is " + args);
}
}
二、后置通知
如何声明后置通知:在切面的一个方法上使用@After注解("切入点表达式")声明。
后置通知何时执行:在目标方法执行之后执行后置通知(无论是否出现异常)。
后置通知中不能访问目标方法的返回结果,因为执行方法可能会抛出异常。
举个例子:
/**
* @Component: 把该放入Spring IOC容器中
* @Aspect: 声明该类为一个切面(需要先放入Spring IOC容器中)
*/
@Aspect
@Component
public class loggingAspect
{
/**
* 后置通知: 在目标方法执行之后执行后置通知(无论是否出现异常)。
* 在后置通知中不能访问目标方法的执行结果,因为方法执行可能会出异常。
*/
@After("execution(* com.sqp.spring.aop.dao.MyCalculator.*(..))")
public void afterAdvice(JoinPoint joinPoint)
{
//获取方法名
String methodName = joinPoint.getSignature().getName();
System.out.println("The method [" + methodName + "] ends.");
}
}
三、返回通知
返回通知如何声明:在切面的一个方法上使用 @AfterReturning("切入点表达式")注解声明。
返回通知何时执行:目标方法正常结束后执行返回通知(不出现异常)。
在返回中可以访问目标方法的执行结果:
① 在@AfterReturning注解中添加returning属性。
② 原始的切入点表达式可以声明在value属性中。
③ 在返回通知方法的参数列表中添加一个新的参数(可声明为Object类型),参数名必须与returning属性的值一致。
举个例子:
/**
* @Component: 把该放入Spring IOC容器中
* @Aspect: 声明该类为一个切面(需要先放入Spring IOC容器中)
*/
@Aspect
@Component
public class loggingAspect
{
/**
* 返回通知: 在目标方法执行之后执行后置通知(无论是否出现异常)。
*/
@AfterReturning(value = "execution(* com.sqp.spring.aop.dao.MyCalculator.*(..))",
returning = "result")
public void returnAdvice(JoinPoint joinPoint, Object result)
{
//获取方法名
String methodName = joinPoint.getSignature().getName();
System.out.println("The method [" + methodName + "] output param is " + result);
}
}
四、异常通知
如何声明异常通知:在切面的一个方法中使用 @AfterThrowing("切入点表达式")注解声明。
异常通知何时执行:异常通知在目标方法抛出异常时执行。
如何声明访问目标方法抛出的异常:
① 在@AfterThrowing注解中添加 throwing属性。
② 原始的切入点表达式可以可以声明在value属性中。
在异常通知方法的参数列表中增加一个新的(异常类型的)参数,其名称必须与throwing的值一致。
③ 如果只需要在抛出某种特定异常时,才执行异常通知:
可以将参数声明为特定异常类型,如:NullPointerException。
举个例子:
/**
* @Component: 把该放入Spring IOC容器中
* @Aspect: 声明该类为一个切面(需要先放入Spring IOC容器中)
*/
@Aspect
@Component
public class loggingAspect
{
/**
* 异常通知: 在目标方法执行之后执行后置通知(无论是否出现异常)。
*/
@@AfterThrowing(value = "execution(* com.sqp.spring.aop.dao.MyCalculator.*(..))",
throwing= "ex")
public void returnAdvice(JoinPoint joinPoint, Exception ex)
{
//获取方法名
String methodName = joinPoint.getSignature().getName();
System.out.println("The method [" + methodName + "] occurs exception: " + ex);
}
}
五、环绕通知
环绕通知是所有通知类型中功能最强大的(但不意味着是最常用的),能够全面地控制目标方法(连接点),甚至可以决定是否执行目标方法(连接点)。
环绕通知方法的参数类型必须为 ProceedingJoinPoint,它是 JoinPoint 的子接口,可以决定目标方法是否执行,何时执行。
在环绕通知中,需要明确调用 ProceedingJoinPoint 的 proceed()方法来执行被代理的(目标)方法,如果没有调用 proceed() 方法,就会导致,通知执行了,目标方法没有执行。
环绕通知方法需要有返回值,且返回值必须是执行 proceed() 的结果,否则会抛出空指针异常。
举个例子:
/**
* @Component: 把该放入Spring IOC容器中
* @Aspect: 声明该类为一个切面(需要先放入Spring IOC容器中)
*/
@Aspect
@Component
public class loggingAspect
{
@Around("execution(* com.sqp.spring.aop.dao.MyCalculator.*(..))")
public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint)
{
Object result = null;
String methodName = proceedingJoinPoint.getSignature().getName();
List<Object> args = Arrays.asList(proceedingJoinPoint.getArgs());
try
{
//前置通知
System.out.println("The method [" + methodName + "] input params is " + args);
//执行目标方法
result = proceedingJoinPoint.proceed();
//返回通知
System.out.println("The method [" + methodName + "] output param is " + result);
} catch (Throwable e)
{
//异常通知
System.out.println("The method [" + methodName + "] occurs exception: " + e);
}
//后置通知
System.out.println("The method [" + methodName + "] ends.");
return result;
}
}
最后,曾经项目中编写的用于拦截controller的请求信息,并将请求信息输入到日志文件中的案例
package com.xxx.aspact.log;
import org.aopalliance.intercept.Joinpoint;
import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
/**
* 拦截日志的切面
*/
@Aspect
@Component
public class LogAspace {
private static Logger logger = Logger.getLogger(Logger.class);
/**
* 切入点,拦截范围
* 第一个*表示任何返回类型的方法
* 第二个*表示任意类
* 第三个*表示任意方法,(..)表示任意参数的方法
*/
@Pointcut("execution(public * com.rzx.demo.controller..*.*(..))")
public void webLog(){}
/**
* 配置一个前置通知,在目标方法执行前执行
* 如何声明前置通知:在切面的的一个方法上面使用@Before("切入点表达式")声明。
* 前置通知何时执行:在目标方法执行之前执行。目标方法:切入点表达式中指定的方法。
* AspectJ 切入点表达式的编写:
* execution(* com.sqp.spring.aop.dao.MyCalculator.*(..))
* 第一个 *表示任意修饰符任意返回值。
* 第二个 *表示 com.sqp.spring.aop.dao.MyCalculator类中任意方法。
* (..)表示匹配任意数量的参数。
* 可以在通知方法中声明一个JoinPoint 类型的参数,然后就可以访问连接细节,如方法名,参数。
*/
@Before("webLog()")
public void doBefore(JoinPoint joinPoint){
//如果不在controller中可以通过使用该api获取request和response
ServletRequestAttributes servletRequestAttributes= (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
//获取request
HttpServletRequest request = servletRequestAttributes.getRequest();
logger.info("----------request------url----------"+request.getRequestURL());
logger.info("----------request-------mothod------"+request.getMethod());
logger.info("----------request--------ip----------"+request.getRemoteAddr()+":"+request.getRemotePort());
Enumeration<String> parameterNames = request.getParameterNames();
while (parameterNames.hasMoreElements()){
String key = parameterNames.nextElement();
String value = request.getParameter(key);
logger.info("request------param--------"+key+":"+value);
}
}
/**
* 无论是否异常,当目标方法执行完毕都会调用
* 如何声明后置通知:在切面的一个方法上使用@After注解("切入点表达式")声明。
* 后置通知何时执行:在目标方法执行之后执行后置通知(无论是否出现异常)。
* 后置通知中不能访问目标方法的返回结果,因为执行方法可能会抛出异常。
* @param joinPoint
*/
@After("webLog()")
public void doAffter(JoinPoint joinPoint){
}
/**
* 返回通知,只有目标方法正常执行完毕(不出现异常)后执行
* 方法的参数就是目标方法的返回值,名称要和returning的值一样
* 返回通知如何声明:在切面的一个方法上使用 @AfterReturning("切入点表达式")注解声明。
* 返回通知何时执行:目标方法正常结束后执行返回通知(不出现异常)。
* 在返回中可以访问目标方法的执行结果:
* ① 在@AfterReturning注解中添加returning属性。
* ② 原始的切入点表达式可以声明在value属性中。
* ③ 在返回通知方法的参数列表中添加一个新的参数(可声明为Object类型),参数名必须与returning属性的值一致。
*/
@AfterReturning(returning = "ret",pointcut = "webLog()")
public void AfterReturning(JoinPoint joinPoint,Object ret){
}
/**
* 异常通知
* 如何声明异常通知:在切面的一个方法中使用 @AfterThrowing("切入点表达式")注解声明。
* 异常通知何时执行:异常通知在目标方法抛出异常时执行。
* 如何声明访问目标方法抛出的异常:
* ① 在@AfterThrowing注解中添加 throwing属性。
* ② 原始的切入点表达式可以可以声明在value属性中。
* 在异常通知方法的参数列表中增加一个新的(异常类型的)参数,其名称必须与throwing的值一致。
* ③ 如果只需要在抛出某种特定异常时,才执行异常通知:
* 可以将参数声明为特定异常类型,如:NullPointerException。
*/
@AfterThrowing(pointcut = "webLog()",throwing = "ex")
public void doAfterThrowing(JoinPoint joinPoint,Exception ex){
}
/**
* 环绕通知,可以控制目标方法的执行时机,调用目标方法前和目标方法后(不出现异常)
* 都会回调改方法,方法中必须手动指定执行目标方法process():
* 必须有返回值,否则目标方法正常调用完毕出现空指针
* 1.环绕通知是所有通知类型中功能最强大的(但不意味着是最常用的),能够全面地控制目标方法(连接点),甚至可以决定是否执行目标方法(连接点)。
* 2.环绕通知方法的参数类型必须为 ProceedingJoinPoint,它是 JoinPoint 的子接口,可以决定目标方法是否执行,何时执行。
* 3.在环绕通知中,需要明确调用 ProceedingJoinPoint 的 proceed()方法来执行被代理的(目标)方法,如果没有调用 proceed() 方法,就会导致,通知执行了,目标方法没有执行。
* 4.环绕通知方法需要有返回值,且返回值必须是执行 proceed() 的结果,否则会抛出空指针异常。
* @param proceedingJoinPoint
*/
@Around("webLog()")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint){
//手动调用目标方法
try {
return proceedingJoinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return null;
}
}