Spring AOP 使用示例
原创2019/2/13大约 2 分钟
Spring AOP 使用示例
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
@Aspect
@Component
@Slf4j
public class LogAspect {
// 定义切入点,提供一个方法,这个方法的名字就是改切入点的id,当然了,还可以有多个切入点
@Pointcut("execution(* com.jastarwang.demo.web.controller..*(..))")
private void allMethod() {
}
// 带匹配名字的表达式
@Pointcut("(execution(* com.jastarwang.demo..*Controller.save*(..))) || (execution(* com.jastarwang.demo..*Controller.update*(..)))")
private void otherMethod() {
}
// 针对指定的切入点表达式选择的切入点应用前置通知
@Before("allMethod()")
public void before(JoinPoint call) {
// 类名
String className = call.getTarget().getClass().getName();
// 方法名
String methodName = call.getSignature().getName();
// 入参
Object[] args = call.getArgs();
if (args != null && args.length > 0) {
for (Object arg : args) {
if (arg == null || arg instanceof HttpSession || arg instanceof HttpServletRequest
|| arg instanceof HttpServletResponse) {
continue;
}
log.info("【controller.log.param】->" + className + "->" + methodName + "->" + arg);
}
}
}
// 访问命名切入点来应用后置通知
@AfterReturning(returning = "rvt", pointcut = "allMethod()")
public void afterReturn(JoinPoint call, Object rvt) {
// 类名
String className = call.getTarget().getClass().getName();
// 方法名
String methodName = call.getSignature().getName();
// 出参
if (rvt != null) {
log.info("【controller.log.return】->" + className + "->" + methodName + "->" + rvt);
}
}
// 应用最终通知
@After("allMethod()")
public void after() {
// log.info("【注解-最终通知】:不管方法有没有正常执行完成,最终一定会通过此方法返回的");
}
// 应用异常抛出后通知
@AfterThrowing("allMethod()")
public void afterThrowing() {
// log.info("【注解-异常抛出后通知】:方法执行时出异常了");
}
// 应用周围通知(有时候想改变入参或出参可以使用此方式)
// @Around("allMethod()")
public Object doAround(ProceedingJoinPoint call) throws Throwable {
Object result = null;
this.before(call);// 相当于前置通知
try {
result = call.proceed();
this.afterReturn(call, result); // 相当于后置通知
} catch (Throwable e) {
this.afterThrowing();
// 相当于异常抛出后通知
throw e;
} finally {
this.after(); // 相当于最终通知
}
return result;
}
}还可以使用自定义注解来定义切入点,用起来也比较方便:
@Pointcut("@annotation(com.jastarwang.demo.annotion.BussinessLog)")
public void annoationMethod() {
}
@Before("annoationMethod()")
public void before(JoinPoint call) {
try {
// 获取拦截的方法对象
if (!(call.getSignature() instanceof MethodSignature)) {
throw new IllegalArgumentException("该注解只能用于方法");
}
MethodSignature msig = (MethodSignature) call.getSignature();
Method method = call.getTarget().getClass().getMethod(msig.getName(), msig.getParameterTypes());
// 获取该方法的自定义注解
BussinessLog annotation = method.getAnnotation(BussinessLog.class);
// do something...
} catch (Exception e) {
e.printStackTrace();
}
}对应的注解类:
/**
* 在此仅做示例
*/
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface BussinessLog {
/**
* 业务的名称,例如:"修改菜单"
*/
String value() default "";
/**
* 被修改的实体的唯一标识,例如:菜单实体的唯一标识为"id"
*/
String key() default "id";
/**
* 字典(用于查找key的中文名称和字段的中文名称)
*/
Class<? extends AbstractDictMap> dict() default SystemDict.class;
}小提示:
- 自定义注解只有一个属性时,且属性名为 value 时,赋值时 value 可省略。
- 自定义注解只有一个属性时,且属性名不为 value 时,赋值时应与属性名保持一致。
- 自定义注解有多个属性时,赋值时应与属性名保持一致。

