`

Spring 自定义注解实现操作日志记录功能

 
阅读更多
Spring 自定义注解实现操作日志记录功能

最近项目组长分配给我一个任务,让我完成一个操作日志的管理功能。需求是这样的:项目很大,有好几个子系统,而且这些子系统已经都在开发过程中了,都进行了大半了。现在要实现的操作日志管理是要将用户在登录系统后所做的重要操作记录下来并查询。记录的内容包括操作人的相关信息(比如:用户ID,用户名,用户IP地址,所属机构等)和所执行的操作的相关信息(比如:所属模块名称、类名、方法名、参数、是否操作成功、描述信息和错误信息)。操作日志查询功能没有什么可说的,难点是在于操作日志的记录,首先要考虑到的是日志的记录不能或要尽量少地让其他人改动自己的代码;其次要考虑到日志记录的灵活性。因此我采用了注解的方式来实现,只要将注解添加到想要记录日志的方法体上就可以实现操作日志的记录。

实现步骤如下:

第一步:启用@AspectJ支持。
在你的工程lib目录下添加aspectjweaver.jar和aspectjrt.jar并在spring 的配置文件中添加如下代码:
<aop:aspectj-autoproxy/>  


这一步就完成了@AspectJ的支持,从而可以实现通过注解方式将通知编织到非公共方法中。

第二步:编写自定义注解。实现对方法所实现的功能进行描述,以便在通知中获取描述信息,代码非常简单,如下:
package com.abchina.rmpf.logmng.ann;  
  
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 rmpfLog {  
    String desc() default "无描述信息";  
}  


第三步:编写操作日志切面通知实现类。
在编写切面通知实现类之前,我们需要搞清楚我们需要哪些通知类型,是前置通知、后置通知、环绕通知或异常通知?根据我的需求,我们知道我们记录的操作日志有两种情况,一种是操作成功,一种是操作失败。操作成功时则方法肯定已经执行完成,顾我们需要实现一个后置通知;操作失败时则说明方法出现异常无法正常执行完成,顾还需要一个异常通知。因此我们就需要实现这两个通知即可。代码如下:
package com.abchina.rmpf.logmng.aop;  
  
import java.io.File;  
import java.lang.reflect.Array;  
import java.util.Collection;  
import java.util.Iterator;  
  
import net.sf.json.JSONArray;  
import net.sf.json.JSONObject;  
  
import org.aspectj.lang.JoinPoint;  
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.dom4j.Document;  
import org.dom4j.Element;  
import org.dom4j.io.SAXReader;  
import org.springframework.util.ResourceUtils;  
  
import plantix.core.business.exception.BusinessException;  
import plantix.core.context.ThreadContext;  
  
import com.abchina.rmpf.privmng.web.vo.ActorVO;  
import com.abchina.rmpf.common.Constant;  
import com.abchina.rmpf.common.DateTool;  
import com.abchina.rmpf.logmng.ann.rmpfLog;  
import com.abchina.rmpf.logmng.service.ILogService;  
import com.abchina.rmpf.logmng.web.vo.LogVO;  
import com.opensymphony.xwork2.ActionContext;  
  
@Aspect //该注解标示该类为切面类  
public class LogAspect {  
      
    /** 
     * LogService 
     * @generated 
     */  
    private ILogService logService;  
      
    //标注该方法体为后置通知,当目标方法执行成功后执行该方法体  
    @AfterReturning("within(com.abchina.irms..*) && @annotation(rl)")  
    public void addLogSuccess(JoinPoint jp, rmpfLog rl){  
        Object[] parames = jp.getArgs();//获取目标方法体参数  
        String params = parseParames(parames); //解析目标方法体的参数  
        String className = jp.getTarget().getClass().toString();//获取目标类名  
        className = className.substring(className.indexOf("com"));  
        String signature = jp.getSignature().toString();//获取目标方法签名  
        String methodName = signature.substring(signature.lastIndexOf(".")+1, signature.indexOf("("));  
        String modelName = getModelName(className); //根据类名获取所属的模块  
          
        String ip = (String)ActionContext.getContext().getSession().get(Constant.THREAD_APP_IP_KEY); //用户IP  
        ActorVO actor = ((ActorVO)ActionContext.getContext().getSession().get(Constant.SESSION_ACTOR_KEY));  
        LogVO logvo = new LogVO();  
        logvo.setId(java.util.UUID.randomUUID().toString());  
        logvo.setClassname(className);  
        logvo.setMethodname(methodName);  
        logvo.setArgument(params);  
        logvo.setMemo(rl.desc());  
        logvo.setModelname(modelName);  
        logvo.setIp(ip);  
        logvo.setOperationtime(DateTool.getDateTime4());  
//      logvo.setErr("");  
        logvo.setFlag("1");  
          
        if(actor!=null){  
            logvo.setOrgid(actor.getOrgcode());  
            logvo.setUserid(actor.getUserid());  
            logvo.setUsername(actor.getUsername());  
        }  
          
        logService.insertLog(logvo);  
    }  
  
    //标注该方法体为异常通知,当目标方法出现异常时,执行该方法体  
    @AfterThrowing(pointcut="within(com.abchina.irms..*) && @annotation(rl)", throwing="ex")  
    public void addLog(JoinPoint jp, rmpfLog rl, BusinessException ex){  
        Object[] parames = jp.getArgs();  
        String params = parseParames(parames);  
        String className = jp.getTarget().getClass().toString();  
        className = className.substring(className.indexOf("com"));  
        String signature = jp.getSignature().toString();  
        String methodName = signature.substring(signature.lastIndexOf(".")+1, signature.indexOf("("));  
        String modelName = getModelName(className);  
          
        String ip = (String)ActionContext.getContext().getSession().get(Constant.THREAD_APP_IP_KEY);  
        ActorVO actor = ((ActorVO)ActionContext.getContext().getSession().get(Constant.SESSION_ACTOR_KEY));  
        LogVO logvo = new LogVO();  
        logvo.setId(java.util.UUID.randomUUID().toString());  
        logvo.setClassname(className);  
        logvo.setMethodname(methodName);  
        logvo.setArgument(params);  
        logvo.setMemo(rl.desc());  
        logvo.setModelname(modelName);  
        logvo.setIp(ip);  
        logvo.setOperationtime(DateTool.getDateTime4());  
        logvo.setErr(ex.toString());//记录异常信息  
        logvo.setFlag("0");  
          
        if(actor!=null){  
            logvo.setOrgid(actor.getOrgcode());  
            logvo.setUserid(actor.getUserid());  
            logvo.setUsername(actor.getUsername());  
        }  
          
        logService.insertLog(logvo);  
    }  
      
    /** 
     * 根据包名查询检索其所属的模块 
     * @param packageName 包名 
     * @return 模块名称 
     */  
    private String getModelName(String packageName){  
        String modelName = "";  
        SAXReader reader = new SAXReader();  
        try {  
            //读取project.xml模块信息描述xml文档  
            File proj = ResourceUtils.getFile("classpath:project.xml");  
            Document doc = reader.read(proj);  
            //获取文档根节点  
            Element root = doc.getRootElement();  
            //查询模块名称  
            modelName = searchModelName(root, packageName);  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
        return modelName;  
    }  
      
    /** 
     * 采用递归方式根据包名逐级检索所属模块 
     * @param element 元素节点 
     * @param packageName 包名 
     * @return 模块名称 
     */  
    private String searchModelName(Element element, String packageName){  
        String modelName = searchModelNodes(element, packageName);  
        //若将包名解析到最后的根目录后仍未检索到模块名称,则返回空  
        if(packageName.lastIndexOf(".")==-1){  
            return modelName;  
        }  
        //逐级检索模块名称  
        if(modelName.equals("")){  
            packageName = packageName.substring(0, packageName.lastIndexOf("."));  
            modelName = searchModelName(element, packageName);  
        }  
        return modelName;  
    }  
      
      
    /** 
     * 根据xml文档逐个节点检索模块名称 
     * @param element 节点元素 
     * @param packageName 包名 
     * @return 模块名称 
     */  
    @SuppressWarnings("unchecked")  
    private String searchModelNodes(Element element, String packageName){  
          
        String modelName = "";  
        Element modules = element.element("modules");  
        Iterator it = modules.elementIterator();  
        if(!it.hasNext()){  
            return modelName;  
        }  
        while (it.hasNext()) {  
            Element model = (Element) it.next();  
            String pack = model.attributeValue("packageName");  
            String name = model.elementText("moduleCHPath");  
            if(packageName.equals(pack)){  
                modelName = name;  
                return modelName;  
            }  
            if(modelName!=null && !modelName.equals("")){  
                break;  
            }  
            modelName = searchModelNodes(model, packageName);  
        }  
          
        return modelName;  
    }  
      
    /** 
     * 解析方法参数 
     * @param parames 方法参数 
     * @return 解析后的方法参数 
     */  
    private String parseParames(Object[] parames) {  
        StringBuffer sb = new StringBuffer();  
        for(int i=0; i<parames.length; i++){  
            if(parames[i] instanceof Object[] || parames[i] instanceof Collection){  
                JSONArray json = JSONArray.fromObject(parames[i]);  
                if(i==parames.length-1){  
                    sb.append(json.toString());  
                }else{  
                    sb.append(json.toString() + ",");  
                }  
            }else{  
                JSONObject json = JSONObject.fromObject(parames[i]);  
                if(i==parames.length-1){  
                    sb.append(json.toString());  
                }else{  
                    sb.append(json.toString() + ",");  
                }  
            }  
        }  
        String params = sb.toString();  
        params = params.replaceAll("(\"\\w+\":\"\",)", "");  
        params = params.replaceAll("(,\"\\w+\":\"\")", "");  
        return params;  
    }  
  
    public ILogService getLogService() {  
        return logService;  
    }  
  
    public void setLogService(ILogService logService) {  
        this.logService = logService;  
    }  
      
      
}  


大家看上面的代码会发现这两个方法体:
@AfterReturning("within(com.abchina.irms..*) && @annotation(rl)")  
public void addLogSuccess(JoinPoint jp, rmpfLog rl){…}  


@AfterThrowing(pointcut="within(com.abchina.irms..*) && @annotation(rl)", throwing="ex")  
public void addLog(JoinPoint jp, rmpfLog rl, BusinessException ex){…}  


这两个方法体分别是后置通知和异常通知的实现。它们有两个相同的参数jp和rl,jp是切点对象,通过该对象可以获取切点所切入方法所在的类,方法名、参数等信息,具体方法可以看方法体的实现;rl则是我们的自定义注解的对象,通过该对象我们可以获取注解中参数值,从而获取方法的描述信息。在异常通知中多出了一个ex参数,该参数是方法执行时所抛出的异常,从而可以获取相应的异常信息。此处为我写的自定义异常。注意:如果指定异常参数,则异常对象必须与通知所切入的方法体抛出的异常保持一致,否则该通知不会执行。

addLogSuccess方法签名上的@AfterReturning("within(com.abchina.irms..*) && @annotation(rl)")注解,是指定该方法体为后置通知,其有一个表达式参数,用来检索符合条件的切点。该表达式指定com/abchina/irms目录下及其所有子目录下的所有带有@rmpfLog注解的方法体为切点。
addLog方法签名上的@AfterThrowing(pointcut="within(com.abchina.irms..*) && @annotation(rl)", throwing="ex")注解,是指定方法体为异常通知,其有一个表达式参数和一个抛出异常参数。表达式参数与后置通知的表达式参数含义相同,而抛出异常参数,则表示如果com/abchina/irms目录下及其所有子目录下的所有带有@rmpfLog注解的方法体在执行时抛出BusinessException异常时该通知便会执行。
注意:切面通知实现类是一个普通的pojo对象,如果要想指定其为通知对象,则需在其类名上添加@Aspect注解

第四步:在spring配置文件中创建通知bean对象。
<bean id="logAspect" class="com.abchina.rmpf.logmng.aop.LogAspect">  
    <property name="logService">  
      <ref local="com.abchina.rmpf.logmng.service.impl.LogServiceImpl"/>  
    </property>  
  </bean>  


第五步:使用操作日志记录注解。
通过以上四步操作后,操作日志的记录功能就算完成了,那我们该如何使用呢?很简单!在com/abchina/irms目录下及其所有子目录下任意找到一个service层的某个类的方法,在其方法体上添加@rmpfLog(desc=”描述信息”)即可。代码如下:
@rmpfLog(desc="创建关联交易合同")  
@Transactional  
public void insertRtcont(RtcontVO rtcontVO) throws BusinessException {  
    rtcontAL.insertRtcont(toRtcontDomain(rtcontVO));  
}  


分享到:
评论
1 楼 di1984HIT 2014-04-24  
写的真的很好

相关推荐

    spring aop 自定义注解保存操作日志到mysql数据库 源码

    4、想看spring aop 注解实现记录系统日志并入库等 二、能学到什么 1、收获可用源码 2、能够清楚的知道如何用spring aop实现自定义注解以及注解的逻辑实现 (需要知道原理的请看spring aop源码,此处不做赘述) 3、...

    spring自定义注解样例

    总结来说,Spring自定义注解和AOP的结合使用,让我们能够灵活地在代码中插入跨切面的关注点,如日志记录、事务管理、性能监控等,而不必在每个方法中手动添加这些代码。这不仅提高了代码的整洁度,也使得系统更加...

    Spring 自定义注解的解析

    总的来说,Spring自定义注解的解析是一个强大且灵活的工具,可以帮助我们实现更精细化的代码组织和控制。结合`@ComponentScan`,我们可以轻松地在Spring环境中管理和利用自定义注解,进一步提升代码的可读性和可维护...

    SpringMVC利用AOP实现自定义注解记录日志

    总结,Spring MVC结合AOP和自定义注解可以方便地实现日志记录功能,无需侵入业务代码。这种方法具有良好的可扩展性和灵活性,可以轻松适应不同场景的日志需求。同时,通过调整切面的定义,我们可以控制日志记录的...

    spring自定义注解+Aspect使用1

    在本文中,我们将学习如何在 Spring 项目中使用自定义注解和 Aspect 来实现日志记录功能。我们将从头开始,创建一个简单的 Spring Boot 项目,然后使用自定义注解和 Aspect 来实现日志记录。 自定义注解 首先,...

    自定义注解实现伪动态传参的小demo

    自定义注解是扩展Java功能的强大工具,可以用于实现各种目的,如代码生成、编译时检查、运行时处理等。在这个“自定义注解实现伪动态传参的小demo”中,我们将探讨如何创建一个自定义注解,以允许在注解中传递类似于...

    spring中自定义注解(annotation)与AOP中获取注解

    通过这种方式,我们可以在AOP中灵活地处理带有自定义注解的方法,实现如日志记录、性能监控、权限验证等多种功能。这不仅提高了代码的复用性,也使得业务逻辑更加清晰。 总结来说,Spring中的自定义注解和AOP的结合...

    spring AOP自定义注解方式实现日志管理的实例讲解

    在本文中,我们将探讨如何使用 Spring AOP 实现日志管理,并使用自定义注解方式来记录日志信息。这种方式可以灵活地实现日志管理,提高系统的可维护性和可扩展性。 首先,我们需要在 applicationContext-mvc.xml ...

    spring自定义切面实例

    这些方法分别在方法调用前、调用后和抛出异常后执行,实现了全面的日志记录功能。 ### 总结 Spring框架的AOP功能,尤其是@AspectJ的支持,为开发者提供了强大的工具来实现自定义切面。通过合理的切点定义和切面类...

    java(spring boot)自定义注解

    总结来说,这个示例展示了如何在Spring Boot项目中自定义注解,将其应用于Java Bean的方法,以及如何利用AOP来实现全局扫描和自动执行。这样的实践在系统监控、性能分析和故障排查中非常有用,能够帮助开发者更好地...

    Spring java注解,元注解和自定义注解

    通过定义自定义注解,开发者可以为程序添加更多特定意义的信息,并通过Spring的AOP(面向切面编程)功能实现特定的行为。 1. **定义自定义注解** - 使用@interface关键字定义。 - 可以指定注解的属性(通过@...

    spring自定义注解实现拦截器的实现方法

    通过自定义注解,可以针对不同的业务场景灵活地添加功能,比如权限控制、日志记录、事务管理等。本文将详细讨论如何通过自定义注解实现Spring的拦截器功能。 ### 自定义注解 首先,自定义注解是通过Java注解来标记...

    Spring aop 记录操作日志 Aspect 源码

    本篇将深入探讨如何使用Spring AOP来记录操作日志,并通过自定义Aspect和注解实现这一功能。 首先,我们要理解Spring AOP的基本概念。AOP是面向对象编程(OOP)的一种补充,它专注于处理那些横向的、与业务逻辑无关...

    自定义注解+Spring AOP实现记录用户操作日志-附件资源

    自定义注解+Spring AOP实现记录用户操作日志-附件资源

    实现生成自定义注解的实体类

    在服务端开发中,如Spring框架,自定义注解可以用于简化配置,增强功能。例如,可以创建一个用于事务管理的注解: ```java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @...

    springsecurity2 自定义filter实现

    在Spring Security框架中,自定义过滤器是实现特定安全需求的一种常见方式。Spring Security的核心功能是通过一系列的Filter链来处理HTTP请求,这些Filter包括认证、授权等关键操作。本篇我们将深入探讨如何在Spring...

    java springboot架构 自定义注解保存项目业务日志,使用线程池保存到数据库

    总结来说,这个项目展示了如何在Spring Boot环境中通过自定义注解实现业务日志的动态记录,并利用线程池进行异步保存到数据库。这样的设计既保证了系统的高性能,又提供了完整的业务日志追踪能力,是现代Web应用程序...

    Spring boot学习(六)Spring boot实现AOP记录操作日志.pdf

    总结来说,Spring Boot结合AOP能够有效地实现操作日志的自动化记录,通过自定义注解和切面,我们可以轻松地将日志记录集成到业务代码中,同时还能灵活地调整日志级别和输出方式,满足不同场景的需求。

    spring注解管理日志

    3. **自定义注解**:如果你希望对特定的操作或方法记录日志,可以创建自定义注解,并使用AOP(面向切面编程)来处理这些注解。Spring的`@Aspect`注解可以创建一个切面,其中包含`@Before`, `@After`, `@Around`等...

    Spring AOP 自定义注解的实现代码

    自定义注解是 Spring AOP 的一个重要特性,它允许开发者定义自己的注解,以便在编程中实现更多的功能。 在 Spring AOP 中,自定义注解是通过 @interface 关键字来定义的。例如,在文章中,定义了一个名为 ...

Global site tag (gtag.js) - Google Analytics