一、写一个自定义注解
注解中包括配置方法所在模块名称,以及功能名称,当然我们在注解里可以自定义。
import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Log { /** 模块 */ String title() default ""; /** 功能 */ String action() default ""; }
二、建切面类
切面类里面定义好切点配置,以及所有的需要实现的通知方法。
import java.lang.reflect.Method; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; @Aspect @Component("logAspect") public class LogAspect { private static final Logger log = LoggerFactory.getLogger(LogAspect.class); // 配置织入点 @Pointcut("@annotation(com.test.aspact.Log)") public void logPointCut() { } /** * 前置通知 用于拦截操作,在方法返回后执行 * @param joinPoint 切点 */ @AfterReturning(pointcut = "logPointCut()") public void doBefore(JoinPoint joinPoint) { handleLog(joinPoint, null); } /** * 拦截异常操作,有异常时执行 * * @param joinPoint * @param e */ @AfterThrowing(value = "logPointCut()", throwing = "e") public void doAfter(JoinPoint joinPoint, Exception e) { handleLog(joinPoint, e); } private void handleLog(JoinPoint joinPoint, Exception e) { try { // 获得注解 Log controllerLog = getAnnotationLog(joinPoint); if (controllerLog == null) { return; } // 获得方法名称 String className = joinPoint.getTarget().getClass().getName(); String methodName = joinPoint.getSignature().getName(); String action = controllerLog.action(); String title = controllerLog.title(); //打印日志,如有需要还可以存入数据库 log.info(">>>>>>>>>>>>>模块名称:{}",title); log.info(">>>>>>>>>>>>>操作名称:{}",action); log.info(">>>>>>>>>>>>>类名:{}",className); log.info(">>>>>>>>>>>>>方法名:{}",methodName); } catch (Exception exp) { // 记录本地异常日志 log.error("==前置通知异常=="); log.error("异常信息:{}", exp.getMessage()); exp.printStackTrace(); } } /** * 是否存在注解,如果存在就获取 */ private static Log getAnnotationLog(JoinPoint joinPoint) throws Exception { Signature signature = joinPoint.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; Method method = methodSignature.getMethod(); if (method != null) { return method.getAnnotation(Log.class); } return null; } }
三、spring.xml配置
在spring的配置文件中,开启注解的扫描,开启切面扫描。
<context:component-scan base-package="com.test.**"/> <mvc:annotation-driven /> <aop:aspectj-autoproxy /> <aop:config proxy-target-class="true"></aop:config>
四、测试Controller
@Controller public class HelloController { private static final Logger log = LoggerFactory.getLogger(HelloController.class); @RequestMapping("/hello") //对应的自定义注解,当方法上写这个注解时,就会进入切面类中 @Log(title="哈喽模块",action="say哈喽") public String sayHello() { log.info("HelloController sayHello:{}","hello world!"); return "hello"; } }
测试结果:
输入网址http://localhost/test-demo/hello,进入hello.jsp
控制台日志
看结果可知,先输出了方法里的日志,在返回之后进入了切面方法,打印一出了自定义注解的信息,以及方法具体信息。
五、切面类注解
@Aspect 声明切面,修饰切面类,从而获得 通知。
@Before 前置通知,在目标方法开始之前执行
@AfterReturning 后置通知
@Around 环绕 注意:环绕通知内部一定要确保执行proceed()该方法,如果不执行该方法,业务bean中被拦截的方法就不会被执行。当执行该方法,如果后面还有切面的话,它的执行顺序应该是这样的:先执行后面的切面,如果后面没有切面了,再执行最终的目标对象的业务方法。若不执行该方法,则后面的切面,业务bean的方法都不会被执行。
其实我们仅使用环绕通知就可以实现前置通知、后置通知、异常通知、最终通知等的效果。
@AfterThrowing 抛出异常
@After 最终
@PointCut ,切入点,修饰方法 private void xxx(){} 之后通过“方法名”获得切入点引用
上面例子中用到了 @AfterReturning @AfterThrowing,其他的配置方向差异不大,读者可自行配置