Spring AOP 實現業務和異常日誌記錄實戰

1 業務需求:今日,公司要求對操作的業務和日誌統一做處理,需要把業務表數據相關信息存入日誌表中,比如表名,方法名,業務id,操作操作時間modifyTIme等等。

除了在業務主動插入日誌數據之外,有個比較好的方法就是用面向切面aop處理,明確跟業務邏輯分開,把業務模塊所共同調用的邏輯或責任封裝起來,便於減少系統的重複代碼,降低模塊之間的耦合度,並有利於未來的可操作性和可維護性。

2 業務開發,這邊處理的方式是用方式1【兩種方式 1 利用註解方式 2 通過xml配置】

  • 2.1 定義切入點接口類
package com.hec.dup.facade.mgr.annotation;

import java.lang.annotation.*;

/**
 * 標記需要做業務日誌的方法
 */
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface OperateLogAnnotation {

    /**
     * 被修改的實體的唯一標識,例如:菜單實體的唯一標識爲"id"
            */
    String key() default "id";

    /**
     * 業務模塊類型,例如:"01菜單管理"
     */
    String moduleType()  default "";

    /**
     * 業務模塊名稱,例如:"菜單管理"
     */
    String moduleName()  default "";

    /**
     * 業務模塊功能名稱,例如:"新增菜單功能"
     */
    String functionName()  default "";

    /**
     * 操作日誌內容,例如:"修改菜單"
     */
    String operateContent() default "";

    /**
     * 業務模塊表名,例如:"SYS_MENU"
     */
    String tableName() default "";

}
  • 2.2 定義日誌記錄切面類
package com.hec.dup.facade.mgr.aspect;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.hec.dup.beans.base.PrjUserEntity;
import com.hec.dup.beans.base.vo.PrjCurrUserInfo;
import com.hec.dup.beans.com.DupComOperateLogEntity;
import com.hec.dup.common.utils.UuidUtil;
import com.hec.dup.facade.base.IPrjUserService;
import com.hec.dup.facade.com.IDupComOperateLogService;
import com.hec.dup.facade.mgr.annotation.OperateLogAnnotation;
import org.apache.shiro.SecurityUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

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

/**
 * 日誌記錄切面
 * @author
 */
@Aspect
@Component
public class OperateLogAspect {

    private Logger log = LoggerFactory.getLogger(this.getClass());

    @Autowired(required = false)
    private IDupComOperateLogService operateLogService;
    @Autowired(required = false)
    private IPrjUserService prjUserService;

    /**
     * 通過AOP方式,攔解註解的日誌 
     * 後置通知:如果需要訪問其他建議類型的連接點上下文,則應使用JoinPoint參數類型而不是ProceedingJoinPoint。
     */
    @Pointcut(value = "@annotation(com.hec.dup.facade.mgr.annotation.OperateLogAnnotation)")
    public void logAspect() {
    }

    @After("logAspect()")
    public Object recordLog(JoinPoint point) throws Throwable {
        //先執行業務
        Object result = point.proceed();
        try {
            this.handle(point);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("日誌記錄出錯!", e);
        }
        return result;
    }

    /**
     * 獲取攔截方法參數,處理日誌
     * @param point
     * @throws Exception
     */
    private void handle(ProceedingJoinPoint point) throws Exception {
        //獲取攔截的方法名
        Signature sig = point.getSignature();
        MethodSignature msig = null;
        if (!(sig instanceof MethodSignature)) {
            throw new IllegalArgumentException("該註解只能用於方法");
        }
        msig = (MethodSignature) sig;
        Object[] params = point.getArgs();
        Object target = point.getTarget();
        Method method = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());

        //如果當前用戶未登錄,不做日誌
        PrjCurrUserInfo userInfo = (PrjCurrUserInfo)SecurityUtils.getSubject().getPrincipal();
        if (null == userInfo) {
            return;
        }
        //獲取攔截方法的參數,獲取登錄用戶對象
        PrjUserEntity userEntity = userInfo.getUserEntity();
                /**
                // 攔截的實體類
        Object target = joinPoint.getTarget();
        // 攔截的方法名稱
        String methodName = joinPoint.getSignature().getName();
        // 攔截的方法參數
        Object[] args = joinPoint.getArgs();
        // 攔截的參數類型
        Class[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getMethod().getParameterTypes();
        **/
        //獲取註解日誌內容
        OperateLogAnnotation annotation = method.getAnnotation(OperateLogAnnotation.class);
        //字典,替換成用table查詢表註釋
        //Class dictClass = annotation.dict();
        //通過表名,動態獲取表字段名和註釋
       /* Map<String,String> metaMap = new HashMap<>();
        //MetaTableVo metaTableVo = metaTableService.getByTableName(table);
        if(StringUtil.isNotEmpty(table)){
            List<MetaColumVo> metaColumns = metaColumService.queryByTable(table);
            if(metaColumns!=null && metaColumns.size()>0){
                for(MetaColumVo vo:metaColumns){
                    metaMap.put(vo.getColumnName(),vo.getColumnAlias());
                }
            }
        }*/

        StringBuilder sb = new StringBuilder();
        for (Object param : params) {
            sb.append(param);
            sb.append(" & ");
        }

        //如果涉及到修改,比對修改前後值的變化內容
        String logmsg ="";
        /*if (methodName.indexOf("update") != -1 || methodName.indexOf("modify") != -1
                || methodName.indexOf("edit") != -1) {
            Map<String, String> newMap = HttpKit.getRequestParameters(); //對象被修改後的內容
            Object oldObj = redisSupport.getObject(HttpKit.getRequest().getSession().getId());
            //Object oldObj = LogObjectHolder.me().get();  //獲取爲null
            logmsg = ContrastObjFactory.contrastObj(table,key,metaMap,newMap,oldObj);
        } else {
            logmsg=content;
        }*/
        //記錄操作日誌
        DupComOperateLogEntity operateLogEntity = getOperaLog( userEntity, target, method, logmsg, params );
        operateLogService.save(operateLogEntity);
    }

    /**
     * 封裝操作日誌實體對象
     *
     * @Date 2017/3/30 18:45
     */
    public static DupComOperateLogEntity getOperaLog(PrjUserEntity userEntity, Object target, Method method, String msg, Object[] params) {
        String classPath = target.getClass().getName(); //類名稱,含路徑
        String classMethod = method.getName(); //方法名(英文)
        OperateLogAnnotation annotation = method.getAnnotation( OperateLogAnnotation.class );

        DupComOperateLogEntity operaLog = new DupComOperateLogEntity();
        operaLog.setId( UuidUtil.getUuid() ); //主鍵
        operaLog.setClassPath( classPath ); //類名稱,含路徑
        operaLog.setClassMethod( classMethod ); //方法名(英文)
        operaLog.setIpHost( userEntity.getLastLoginIp() ); //IP地址
        operaLog.setOperateTime( new Date() ); //操作時間
        operaLog.setOperateUserId( userEntity.getId() );//用戶ID
        operaLog.setOperateUserName( userEntity.getUserName() ); //用戶名稱
        operaLog.setUserAccount( userEntity.getAccount() ); //用戶帳號
        operaLog.setUserType( userEntity.getUserType() ); //帳號類型
        operaLog.setModuleType( annotation.moduleType() );//業務模塊類型
        operaLog.setModuleName( annotation.moduleName() );//業務模塊名稱
        operaLog.setFunctionName( annotation.functionName() );//業務模塊名稱
        operaLog.setTableName( annotation.tableName() ); //操作表名

        JSONArray paramArray = JSONObject.parseArray( params.toString() ); //獲取第一個參數的id
        operaLog.setTableId( paramArray.getJSONObject( 0 ).getString( "id" ) );

        operaLog.setOperateContent( annotation.operateContent() ); //操作內容
        return operaLog;
    }

}
  • 2.3 定義applicationContext.xml配置
    Spring AOP 實現業務和異常日誌記錄實戰

  • 2.4 接口添加註解,接口被調用的時候會調用被aop處理
    @OperateLogAnnotation(moduleType = "01")  //這邊添加切入點接口的註解
    @RequestMapping(value = "testAop01")
    @ResponseBody
    public JsonResult testAop(BaseEntity baseEntity) {
        JsonResult jr = new JsonResult();
        baseEntity.setCreateTime( new Date() );
        baseEntity.setDbUser( "123456789," );
        baseEntity.setExport( true );
        jr.setData( baseEntity );
        return jr;
    }

3 注意事項:該方式主要是通過註解的方式,個人覺得比較便利,當然也可以通過另外一種方式xml,比如 AOP實現方式3——通過<aop:config>來配置 ,需要注意的是aop的執行順序,可參考Spring AOP @Before @Around @After 等 advice 的執行順序

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章