在现实的上线中我们经常会遇到系统出现异常或者问题。这个时候就马上打开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 -->
其他的还跟上面的方式一样。这样同样可以正常使用,谢谢。
相关推荐
Spring AOP(Aspect Oriented Programming,面向切面编程)是Spring框架的一个重要组成部分,它允许我们通过定义“切面”来模块化横切关注点,比如日志、事务管理、性能监控等。在传统的面向对象编程中,这些关注点...
在Spring MVC框架中,AOP(面向切面编程)是一种强大的工具,用于实现跨切面的关注点,如日志、事务管理、权限控制等。当我们想通过注解方式拦截Controller层的方法时,可能会遇到一些问题。本文将详细介绍如何使用...
Spring AOP(面向切面编程)是Spring框架的重要组成部分,它提供了一种强大的方式来实现横切关注点,如日志、事务管理、性能监控等,而无需侵入业务代码。下面将详细介绍Spring AOP的注解方式和XML配置方式。 ### ...
在Spring MVC框架中,AOP通常用于实现日志记录、事务管理、性能监控等功能。本篇文章将深入探讨如何在Spring MVC中配置和使用基于注解的AOP。 一、Spring AOP基础知识 1. **切面(Aspect)**:切面是关注点的模块...
通过这种方式,我们可以在Spring MVC中利用AOP实现对Controller方法的透明日志管理,不仅记录正常流程,也能捕获和记录异常,提升系统的可维护性和问题排查效率。 在实际项目中,我们可以根据需求进一步定制日志...
在Spring MVC框架中,AOP(面向切面编程)是一种强大的工具,允许我们在不修改源代码的情况下,插入横切关注点,例如日志记录、事务管理等。本篇将深入探讨如何利用AOP和自定义注解来实现日志记录功能。 首先,我们...
在Spring MVC中,AOP通常用于日志记录、事务管理、性能分析等场景。本文将深入探讨在Spring MVC环境中如何配置和使用AOP,以及解决配置无效的问题。 首先,我们要了解AOP的基本概念。AOP的核心是切面(Aspect),它...
4. Spring的AOP(面向切面编程)和AspectJ注解,如@Aspect、@Before、@After等,实现日志记录、事务管理等功能。 5. Hibernate的实体管理,使用@Entity、@Table等注解定义实体类,@Id、@GeneratedValue设置主键生成...
在Spring MVC框架中,AOP(面向切面编程)是一种强大的设计模式,它允许开发者将关注点分离,比如日志、事务管理、安全检查等,从核心业务逻辑中解耦出来。下面是一个简单的Spring MVC中AOP的例子,分为配置和实现两...
在Spring MVC框架中,AOP(面向切面编程)是一种强大的工具,用于实现日志拦截,特别是对于controller层的操作。AOP允许我们定义横切关注点,这些关注点可以是如日志记录、事务管理、权限检查等通用功能,它们在程序...
Spring AOP允许在不修改代码的情况下,向应用程序添加横切关注点,如日志记录、事务管理、性能监控等。 在Spring AOP 4.3.10中,我们主要关注以下几个关键知识点: 1. **切面(Aspect)**:切面是AOP的核心概念,...
Spring AOP 自定义注解方式实现日志管理的实例讲解 在本文中,我们将探讨如何使用 Spring AOP 实现日志管理,并使用自定义注解方式来记录日志信息。这种方式可以灵活地实现日志管理,提高系统的可维护性和可扩展性...
Spring AOP,即Aspect-Oriented Programming(面向切面编程),是Spring框架的重要组成部分,它为应用程序提供了一种模块化和声明式的方式来处理横切关注点,如日志、事务管理、性能监控等。在本"Spring-AOP源码Demo...
Spring Framework 开发参考手册 目录 1. 简介 1.1. 概览 1.1.1. 使用场景 2. Spring 2.0和 2.5的新特性 ...2.5.1. Spring MVC合理的默认值 2.5.2. Portlet 框架 2.5.3. 基于Annotation的控制器 ......
Spring AOP(面向切面编程)是Spring框架的重要组成部分,它提供了一种模块化和声明式的方式来处理系统中的交叉关注点问题,如日志、事务管理等。在Java应用程序中,AOP允许开发者将这些通用功能与核心业务逻辑分离...
总而言之,Spring 4.2.6是一个强大的版本,它集成了AspectJ以增强AOP能力,优化了依赖注入,支持了Java 8的新特性,并改进了各种框架组件,包括MVC、IoC、事务管理和测试框架。这个版本的发布旨在提供更高效、更易于...
Spring AOP(面向切面编程)是Spring框架的重要组成部分,它允许我们在不修改源代码的情况下对应用程序的行为进行统一管理和控制。在本实例中,我们将深入探讨如何使用AspectJ技术和XML配置来实现AOP。 首先,了解...
6. **@AspectJ注解驱动的AOP**:在Spring中,可以使用@AspectJ注解来定义切面。@Aspect注解标记切面类,@Before、@After、@Around等注解定义通知。这种方式使得切面的声明更直观,代码更简洁。 7. **SpringMVC**:...
在实际项目中,我们通常会结合Spring Boot和Spring MVC,通过`@Controller`、`@Service`、`@Repository`等注解来定义我们的业务层,然后通过AOP注解来增强这些服务。例如,我们可以在`@Service`类中定义业务逻辑方法...