利用spring mvc AOP进行日志管理 (采用注解@AspectJ)

      在现实的上线中我们经常会遇到系统出现异常或者问题。这个时候就马上打开CRT或者SSH连上服务器拿日子来分析。受网络的各种限制。于是我们就想为什么不能直接在管理后台查看报错的信息呢。于是日志管理就出现了。

      很早之前就有同学问我,如何用spring aop来实现日志管理的问题,现在个人觉得做日志管理最好的是Aop,当然有的人也喜欢用拦截器。

      Aop有的人说拦截不到Controller。有的人说想拦AnnotationMethodHandlerAdapter截到Controller必须得拦截org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter。首先Aop可以拦截到Controller的,这个是毋容置疑的;其次须拦截AnnotationMethodHandlerAdapter也不是必须的。Aop之所以有的人说拦截不到Controller是因为Controller被jdk代理了。我们只要把它交给cglib代理就可以了。

       废话不多说了,下面开始上代码:

 

第一步:编写两个注解

1、拦截controller的注解

package com.ctlovedove.log.annotation;

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;
/**
 * 系统级别的controller层自定义注解
 * <br>拦截Controller
 * @author ct
 *
 */
@Target({ElementType.PARAMETER, ElementType.METHOD})//作用于参数或方法上
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SystemControllerLog {
	String description() default "";
}

 2、拦截service的注解

package com.ctlovedove.log.annotation;

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;

/**
 * 系统级别的service层自定义注解
 * <br>拦截service
 * @author ct
 */
@Target({ElementType.PARAMETER, ElementType.METHOD})//作用于参数或方法上
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SystemServiceLog {
	String description() default "";
}

 

第二步:编写切点类

package com.ctlovedove.log.aspect;

import java.lang.reflect.Method;
import java.util.Date;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
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.json.JSONArray;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.ctlovedove.joke.bean.Manager;
import com.ctlovedove.joke.bean.SystemLog;
import com.ctlovedove.joke.service.SystemLogService;
import com.ctlovedove.log.annotation.SystemControllerLog;
import com.ctlovedove.log.annotation.SystemServiceLog;
import com.ctlovedove.util.IpUtil;

/**
 * 日志管理切点类
 * @author ct
 *
 */
@Aspect
@Component
public class SystemLogAspect {
	//注入Service用于把日志保存数据库 
	@Resource
	private SystemLogService systemLogService;
	//本地异常日志记录对象  
    private  static  final Logger logger = Logger.getLogger(SystemLogAspect.class);
    
    //controller层切入点
    @Pointcut("@annotation(com.ctlovedove.log.annotation.SystemControllerLog)")
    public void controllerAspect() {
    	System.out.println("========controllerAspect===========");
    }
    //service层切入点
    @Pointcut("@annotation(com.ctlovedove.log.annotation.SystemServiceLog)")
    public void serviceAspect() {
    	System.out.println("========serviceAspect===========");
    }
	
    /**
     * 前置通知 用于拦截Controller层操作 
     * @param joinPoint 切点
     */
    @Before("controllerAspect()")
	public void doBefore(JoinPoint joinPoint) {
		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
				.getRequestAttributes()).getRequest();
		// 获取登陆用户信息
		Manager manager = (Manager) request.getSession().getAttribute(
				"currentManager");
		// 获取请求ip
		String ip = IpUtil.getClientIp(request);
		try {
			// *========控制台输出=========*//
			System.out.println("=====前置通知开始=====");
			System.out.println("请求方法:" + (joinPoint.getTarget().getClass().getName() + "."
							+ joinPoint.getSignature().getName() + "()"));
			System.out.println("方法描述:" + getControllerMethodDescription(joinPoint));
			System.out.println("请求人:" + manager.getAccountName());
			System.out.println("请求IP:" + ip);
			// *========数据库日志=========*//
			SystemLog log = new SystemLog();
			log.setDescription(getControllerMethodDescription(joinPoint));
			log.setMethod((joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()"));
			log.setType(0);
			log.setIp(ip);
			log.setExceptionCode(null);
			log.setExceptionDetail(null);
			log.setParams(null);
			log.setCreateUser(manager.getAccountName());
			log.setCreateDate(new Date());
			// 保存数据库
			systemLogService.save(log);
			System.out.println("=====前置通知结束=====");
		} catch (Exception e) {
			// 记录本地异常日志
			logger.error("==前置通知异常==");
			logger.error("异常信息:{}", e);
		}
	}
    
    /**
     * 异常通知 用于拦截service层记录异常日志
     * @param joinPoint
     * @param e
     */
    @AfterThrowing(pointcut="serviceAspect()", throwing="e")
	public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
				.getRequestAttributes()).getRequest();
		// 获取登陆用户信息
		Manager manager = (Manager) request.getSession().getAttribute(
				"currentManager");
		// 获取请求ip
		String ip = IpUtil.getClientIp(request);
		// 获取用户请求方法的参数并序列化为JSON格式字符串
		String params = "";
		Object[] args = joinPoint.getArgs();
		if (args != null) {
			JSONArray jsonArray = new JSONArray();
			jsonArray.put(args);
			params = jsonArray.toString();
		}
		try {
			/* ========控制台输出========= */
			System.out.println("=====异常通知开始=====");
			System.out.println("异常代码:" + e.getClass().getName());
			System.out.println("异常信息:" + e.getMessage());
			System.out.println("异常方法:"
					+ (joinPoint.getTarget().getClass().getName() + "."
							+ joinPoint.getSignature().getName() + "()"));
			System.out.println("方法描述:" + getServiceMthodDescription(joinPoint));
			System.out.println("请求人:" + manager.getAccountName());
			System.out.println("请求IP:" + ip);
			System.out.println("请求参数:" + params);
			/* ==========数据库日志========= */
			SystemLog log = new SystemLog();
			log.setDescription(getServiceMthodDescription(joinPoint));
			log.setExceptionCode(e.getClass().getName());
			log.setType(1);
			log.setExceptionDetail(e.getMessage());
			log.setMethod((joinPoint.getTarget().getClass().getName() + "."
					+ joinPoint.getSignature().getName() + "()"));
			log.setParams(params);
			log.setCreateUser(manager.getAccountName());
			log.setCreateDate(new Date());
			log.setIp(ip);
			// 保存数据库
			systemLogService.save(log);
			System.out.println("=====异常通知结束=====");
		} catch (Exception ex) {
			// 记录本地异常日志
			logger.error("==异常通知异常==");
			logger.error("异常信息:{}", ex);
		}

	}

    /**
     * 获取注解中对方法的描述信息 用于Controller层注解 
     * @param joinPoint 切点 
     * @return 方法描述 
     * @throws Exception 
     */
    public static String getControllerMethodDescription(JoinPoint joinPoint) throws Exception{
    	//获取目标类名
    	String targetName = joinPoint.getTarget().getClass().getName();
    	//获取方法名
    	String methodName = joinPoint.getSignature().getName();
    	//获取相关参数
    	Object[] arguments = joinPoint.getArgs();
    	//生成类对象
    	Class targetClass = Class.forName(targetName);
    	//获取该类中的方法
    	Method[] methods = targetClass.getMethods();
    	
    	String description = "";
    	
    	for(Method method : methods) {
    		if(!method.getName().equals(methodName)) {
    			continue;
    		}
    		Class[] clazzs = method.getParameterTypes();
    		if(clazzs.length != arguments.length) {
    			continue;
    		}
    		description = method.getAnnotation(SystemControllerLog.class).description();
    	}
    	return description;
    }
    
    /**
     * 获取注解中对方法的描述信息 用于service层注解
     * @param joinPoint 切点 
     * @return 方法描述 
     * @throws Exception 
     */
    public static String getServiceMthodDescription(JoinPoint joinPoint) throws Exception{
    	//获取目标类名
    	String targetName = joinPoint.getTarget().getClass().getName();
    	//获取方法名
    	String methodName = joinPoint.getSignature().getName();
    	//获取相关参数
    	Object[] arguments = joinPoint.getArgs();
    	//生成类对象
    	Class targetClass = Class.forName(targetName);
    	//获取该类中的方法
    	Method[] methods = targetClass.getMethods();
    	
    	String description = "";
    	
    	for(Method method : methods) {
    		if(!method.getName().equals(methodName)) {
    			continue;
    		}
    		Class[] clazzs = method.getParameterTypes();
    		if(clazzs.length != arguments.length) {
    			continue;
    		}
    		description = method.getAnnotation(SystemServiceLog.class).description();
    	}
    	return description;
    }
}

 

第三步:将controller的代理权交给cglib。那么此时需要在spring的配置文件中添加如下代码:

<!--启动对@AspectJ注解的支持 , proxy-target-class设置为true表示通知spring使用cglib而不是jdk的来生成代理方法,这样AOP可以拦截到Controller -->
<aop:aspectj-autoproxy proxy-target-class="true" />

 切记,如果你使用的是spring mvc的话,这句一定要写在springmvc的配置文件中,否则会似的@AspectJ不起作用的,我是深受其害的一位。

 

第四步:开始使用注解

在controller或service的方法上添加注解,示例如下:

@RequestMapping("save")
@SystemControllerLog(description="新增笑话")
public String save(JokeInfo jokeInfo, RedirectAttributes attributes, HttpServletRequest request, HttpSession session){
	logger.info("新增笑话--jokeinfo--"+jokeInfo);
	try {
		if(jokeInfo == null){
			attributes.addFlashAttribute("errorMsg", "新增笑话失败");
			return "redirect:list.do";
		}
		if(StringUtil.isNull(jokeInfo.getSource())){
			jokeInfo.setSource(session.getAttribute("currentManager")+"");
		}
		jokeInfo.setSourceIp(IpUtil.getClientIp(request));
		jokeInfo.setPubDate(new Date());
		jokeInfoService.save(jokeInfo);
		attributes.addFlashAttribute("errorMsg", "新增成功");
	} catch (Exception e) {
		logger.error("保存新笑话失败", e);
		attributes.addFlashAttribute("errorMsg", "新增笑话失败");
	}
	return "redirect:list.do";
}

 

最终效果可见下面截图:



 如果操作顺利的话,以上便是完整的步骤了。但是在我的编写与测试过程中,遇到了好多问题,例如:

 

问题1:在启动tomcat的时候,报org.xml.sax.SAXParseException; lineNumber: 23; columnNumber: 53; 元素 "aop:aspectj-autoproxy" 的前缀 "aop" 未绑定

 

通过查询文档发现,在springmvc配置文件中,我们不仅仅要引入Aop的名称空间,还要在xsi:schemaLocation中加入aop的xsd文件 。

 

问题2:AspectJ 出现错误::0 can't find referenced pointcut

原因是我用的jdk7与aspectjrt.jar包不兼容,解决方法是更换jar包。

当环境为:

jdk 1.7, spring version is 3.0+, 如果使用aspectjrt-1.6.2 and aspectjweaver-1.6.2这个版本,就会出现上述错误,将aspectj and aspectjweaver 版本改为1.7.3 or more,问题得到解决。

 

补充:上面是基于注解的方式进行的aop配置,但有些朋友更喜欢使用xml配置文件的方式,比如我,所以下面是部分xml的配置,如有问题,还请大神们指教。

1、首先,将切面类中的SystemLogAspect中的注解去掉。

package com.ctlovedove.log.aspect;

import java.lang.reflect.Method;
import java.util.Date;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.json.JSONArray;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.ctlovedove.joke.bean.Manager;
import com.ctlovedove.joke.bean.SystemLog;
import com.ctlovedove.joke.service.SystemLogService;
import com.ctlovedove.log.annotation.SystemControllerLog;
import com.ctlovedove.log.annotation.SystemServiceLog;
import com.ctlovedove.util.IpUtil;

/**
 * 日志管理切点类
 * @author chenting
 *
 */
public class SystemLogAspect {
	//注入Service用于把日志保存数据库 
	@Resource
	private SystemLogService systemLogService;
	//本地异常日志记录对象  
    private  static  final Logger logger = Logger.getLogger(SystemLogAspect.class);
    
   
    /**
     * 前置通知 用于拦截Controller层操作 
     * @param joinPoint 切点
     */
	public void doBefore(JoinPoint joinPoint) {
		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
				.getRequestAttributes()).getRequest();
		// 获取登陆用户信息
		Manager manager = (Manager) request.getSession().getAttribute(
				"currentManager");
		// 获取请求ip
		String ip = IpUtil.getClientIp(request);
		try {
			// *========控制台输出=========*//
			System.out.println("=====前置通知开始=====");
			System.out.println("请求方法:" + (joinPoint.getTarget().getClass().getName() + "."
							+ joinPoint.getSignature().getName() + "()"));
			System.out.println("方法描述:" + getControllerMethodDescription(joinPoint));
			System.out.println("请求人:" + manager.getAccountName());
			System.out.println("请求IP:" + ip);
			// *========数据库日志=========*//
			SystemLog log = new SystemLog();
			log.setDescription(getControllerMethodDescription(joinPoint));
			log.setMethod((joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()"));
			log.setType(0);
			log.setIp(ip);
			log.setExceptionCode(null);
			log.setExceptionDetail(null);
			log.setParams(null);
			log.setCreateUser(manager.getAccountName());
			log.setCreateDate(new Date());
			// 保存数据库
			systemLogService.save(log);
			System.out.println("=====前置通知结束=====");
		} catch (Exception e) {
			// 记录本地异常日志
			logger.error("==前置通知异常==");
			logger.error("异常信息:{}", e);
		}
	}
    
    /**
     * 异常通知 用于拦截service层记录异常日志
     * @param joinPoint
     * @param e
     */
	public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
				.getRequestAttributes()).getRequest();
		// 获取登陆用户信息
		Manager manager = (Manager) request.getSession().getAttribute(
				"currentManager");
		// 获取请求ip
		String ip = IpUtil.getClientIp(request);
		// 获取用户请求方法的参数并序列化为JSON格式字符串
		String params = "";
		Object[] args = joinPoint.getArgs();
		if (args != null) {
			JSONArray jsonArray = new JSONArray();
			jsonArray.put(args);
			params = jsonArray.toString();
		}
		try {
			/* ========控制台输出========= */
			System.out.println("=====异常通知开始=====");
			System.out.println("异常代码:" + e.getClass().getName());
			System.out.println("异常信息:" + e.getMessage());
			System.out.println("异常方法:"
					+ (joinPoint.getTarget().getClass().getName() + "."
							+ joinPoint.getSignature().getName() + "()"));
			System.out.println("方法描述:" + getServiceMthodDescription(joinPoint));
			System.out.println("请求人:" + manager.getAccountName());
			System.out.println("请求IP:" + ip);
			System.out.println("请求参数:" + params);
			/* ==========数据库日志========= */
			SystemLog log = new SystemLog();
			log.setDescription(getServiceMthodDescription(joinPoint));
			log.setExceptionCode(e.getClass().getName());
			log.setType(1);
			log.setExceptionDetail(e.getMessage());
			log.setMethod((joinPoint.getTarget().getClass().getName() + "."
					+ joinPoint.getSignature().getName() + "()"));
			log.setParams(params);
			log.setCreateUser(manager.getAccountName());
			log.setCreateDate(new Date());
			log.setIp(ip);
			// 保存数据库
			systemLogService.save(log);
			System.out.println("=====异常通知结束=====");
		} catch (Exception ex) {
			// 记录本地异常日志
			logger.error("==异常通知异常==");
			logger.error("异常信息:{}", ex);
		}

	}

    /**
     * 获取注解中对方法的描述信息 用于Controller层注解 
     * @param joinPoint 切点 
     * @return 方法描述 
     * @throws Exception 
     */
    public static String getControllerMethodDescription(JoinPoint joinPoint) throws Exception{
    	//获取目标类名
    	String targetName = joinPoint.getTarget().getClass().getName();
    	//获取方法名
    	String methodName = joinPoint.getSignature().getName();
    	//获取相关参数
    	Object[] arguments = joinPoint.getArgs();
    	//生成类对象
    	Class targetClass = Class.forName(targetName);
    	//获取该类中的方法
    	Method[] methods = targetClass.getMethods();
    	
    	String description = "";
    	
    	for(Method method : methods) {
    		if(!method.getName().equals(methodName)) {
    			continue;
    		}
    		Class[] clazzs = method.getParameterTypes();
    		if(clazzs.length != arguments.length) {//比较方法中参数个数与从切点中获取的参数个数是否相同,原因是方法可以重载哦
    			continue;
    		}
    		description = method.getAnnotation(SystemControllerLog.class).description();
    	}
    	return description;
    }
    
    /**
     * 获取注解中对方法的描述信息 用于service层注解
     * @param joinPoint 切点 
     * @return 方法描述 
     * @throws Exception 
     */
    public static String getServiceMthodDescription(JoinPoint joinPoint) throws Exception{
    	//获取目标类名
    	String targetName = joinPoint.getTarget().getClass().getName();
    	//获取方法名
    	String methodName = joinPoint.getSignature().getName();
    	//获取相关参数
    	Object[] arguments = joinPoint.getArgs();
    	//生成类对象
    	Class targetClass = Class.forName(targetName);
    	//获取该类中的方法
    	Method[] methods = targetClass.getMethods();
    	
    	String description = "";
    	
    	for(Method method : methods) {
    		if(!method.getName().equals(methodName)) {
    			continue;
    		}
    		Class[] clazzs = method.getParameterTypes();
    		if(clazzs.length != arguments.length) {//比较方法中参数个数与从切点中获取的参数个数是否相同,原因是方法可以重载哦
    			continue;
    		}
    		description = method.getAnnotation(SystemServiceLog.class).description();
    	}
    	return description;
    }
}

 2、xml配置文件代码如下(该配置仍然要方在springmvc的配置文件中,因为仍然有部分使用了注解)

<!-- 系统操作日志配置  start -->
	<!-- 声明切面类 -->
	<bean id="SystemLogAspect" class="com.ctlovedove.log.aspect.SystemLogAspect"></bean>
	<aop:config>
		<!-- 声明切面 -->
		<aop:aspect ref="SystemLogAspect">
			<!-- 
				1、pointcut="@annotation(com.ctlovedove.log.annotation.SystemControllerLog)" 表示切入点是注解 
				2、method 指向的方法,是切面类中的方法,表示当程序触发pointcut指向的注解时,aop会启动method方法
			-->
			<aop:before method="doBefore" pointcut="@annotation(com.ctlovedove.log.annotation.SystemControllerLog)"/>
			<!-- <aop:after method="doAfterThrowing" pointcut="@annotation(com.ctlovedove.log.annotation.SystemServiceLog)"/> -->
			<aop:after-throwing method="doAfterThrowing" pointcut="@annotation(com.ctlovedove.log.annotation.SystemServiceLog)" throwing="e"/>
		</aop:aspect>
	</aop:config>
	<!-- 系统操作日志配置  end -->

 

其他的还跟上面的方式一样。这样同样可以正常使用,谢谢。

猜你喜欢

转载自dove19900520.iteye.com/blog/2289806