Spring MVC 中使用AOP 进行统一日志管理--注解实现
1.AOP简介
AOP称为面向切面编程
AOP的基本概念
(1)Aspect(切面):通常是一个类,里面可以定义切入点和通知
(2)JointPoint(连接点):程序执行过程中明确的点,一般是方法的调用
(3)Advice(通知):AOP在特定的切入点上执行的增强处理,有before,after,afterReturning,afterThrowing,around
(4)Pointcut(切入点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式
(5)AOP代理:AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类
2.为什么要这么做?
AOP最常见的用法是进行日志管理和事务管理。现在我们重点说日志,下一节我们再介绍日志。
加入没使用AOP日志,但是为了日常维护方便和查找错误方便,我们要输入日志的话只能使用log.info()输出,关键是每一个需要日志的地方都要进行重复操作,而且不同的人输出的日志格式或者日志信息不统一,不便于使用。所以,我们使用切面,将日志服务作为一个组件动态地切入到我们需要地方,统一管理。
3.实现
我是在以前搭建的Spring MVC 工程基础上实现的日志功能。
首先在原来的基础上添加
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.1</version> </dependency>
这个jar包的版本要和jdk匹配,否则会报错,jdk1.7以上必须使用1.7以上版本,为了避免报错,我直接使用了最高版本的。另外还要保证maven jar 保证有 这两个jar包
4.项目配置
项目配置中特别需要注意的是我是给controller层添加日志服务,而controller层是由Spring MVC 进行管理的,所以我们的日志服务组件也要确保能被spring MVC 扫到,而且要在spring MVC 配置文件中天AOP命名空间的支持,同时开启aop 支持。我之前走了很多弯路,就算因为我把这些配置都写在了spring的配置文件中,导致切面类一直无法生效。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd" > <mvc:annotation-driven /> <!-- 扫描controller(controller层注入) --> <context:component-scan base-package="com.lzl.sss.controller"/> <context:component-scan base-package="com.lzl.sss.aop"/> <aop:aspectj-autoproxy proxy-target-class="true"/> <!-- 视图解析器 --> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/view/"> </property> <property name="suffix" value=".jsp"></property> </bean> </beans>
5.切面类
package com.lzl.sss.aop; import java.io.UnsupportedEncodingException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Enumeration; import javax.servlet.http.HttpServletRequest; 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.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; @Component @Aspect //定义切面类 public class WebLogAspect { private Logger logger = LoggerFactory.getLogger(WebLogAspect.class); //定义切入点 @Pointcut("execution(* com.lzl.sss.controller.*.*(..))") public void log(){ } //定义通知,方法执行前 @Before("log()") public void doBefore(JoinPoint poin) throws UnsupportedEncodingException{ logger.info("方法执行前,当前时间:"+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); // 记录下请求内容 logger.info("请求URL : " + request.getRequestURL().toString()); logger.info("请求方法 : " + request.getMethod()); logger.info("IP地址 : " + request.getRemoteAddr()); Enumeration<String> enu = request.getParameterNames(); while (enu.hasMoreElements()) { String name = (String) enu.nextElement(); logger.info("参数:{},值:{}", name,new String(request.getParameter(name).getBytes("ISO-8859-1"),"utf-8")); } } //定义通知,方法执行后 @After("log()") public void after(JoinPoint poin){ logger.info("方法执行后,当前时间:"+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); } //定义通知,方法返回前 @AfterReturning(pointcut="log()",returning="returnVal") public void AfterReturning(JoinPoint poin){ logger.info("方法返回前,当前时间:"+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); } //定义通知,抛出异常 @AfterThrowing(pointcut="log()",throwing="error") public void AfterThrowing(Throwable error){ logger.info("方法报错,当前时间:"+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); } //定义通知环绕型 @Around("log()") public Object Around (ProceedingJoinPoint pjp) throws Throwable{ logger.info("环绕前:"+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); Object obj= pjp.proceed(); logger.info("环绕后:"+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); return obj; } }
切面类有以下几个注意的点:
(1).切点表达式。
第一个*表示匹配任意的方法返回值,..(两个点)表示零个或多个,上面的第一个..表示controller包及其子包,第二个*表示所有类,第三个*表示所有方法,第二个..表示方法的任意参数个数
(2).环绕型通知
环绕型通知在方法执行前后都会执行。且要添加返回类型,否则会导致连接点执行的方法无返回值。
(3).通知类型的执行顺序
正常情况:around--->before--->要执行的方法--->around---->after--->afterReturnning
报错情况下:around--->before--->要执行的方法--->around---->after--->afterThrowing
(4)注解
@Component 注解将类声明为一个组件,并注入到spring MVC 容器中
@Aspect 将这个bean修饰成一个切面类
6.实现效果