SSM框架_4(添加log日誌管理(aop)+Exception異常統一處理(aop))

2017-05-15(添加log日誌管理(aop)+Exception異常統一處理(aop))

github版本號 b3a44f8a7a4452fd28bf2c4562a3e2a6aa7221dchtml

一、添加log日誌管理(aop)

1.1 Log.java:定義Log註解,Target爲類和方法上

package com.ssm.annotation;

import java.lang.annotation.*;

@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {

	/**
	 * 操做事件,idea test
	 */
	String value();
	/**
	 * 字段組裝描述內容,
	 * 如{"name=名稱","status=狀態,1=成功;2=失敗"},
	 * 表單參數爲:name=張三&status=1這樣生成的描述信息爲:
	 * 名稱=張三,狀態=成功
	 */
	String[] entry() default {};
}

1.2 OperLog.java:model類,即日誌

package com.ssm.model;

import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;

import java.io.Serializable;
import java.util.Date;

/***
 * @description 對應t_log表
 * */
@Data
public class OperLog implements Serializable {

	private static final long serialVersionUID = -8690056878905494181L;

	private Long id;
	private String userId;// '操做用戶ID',
	private String userName;// '操做人名稱',
	@JSONField (format="yyyy-MM-dd HH:mm:ss") 
	private Date operTime;// '操做時間(yyyy-MM-dd HH:mm:ss)',
	private String clientIp;// '客戶端IP',
	private String reqUrl;// 訪問url
	private String method;// 請求方法
	private String operEvent;// 操做事件(刪除,新增,修改,查詢,登陸,退出)',
	private int operStatus;// '操做狀態(1:成功,2:失敗)',
	private String logDesc;// 描述信息',
}

1.3 LogAspect.java:日誌切面

Pointcut爲匹配含有Log註解的類和方法,同時將某些重要參數,好比操做用戶ID、操做人名稱(若是已登陸的話)、操做時間(yyyy-MM-dd HH:mm:ss)、客戶端IP、訪問url、請求方法、操做事件(刪除,新增,修改,查詢,登陸,退出等,即Log註解中的value的值)、操做狀態、操做狀態等存到數據庫中。java

package com.ssm.annotation;

import com.ssm.model.OperLog;
import com.ssm.model.User;
import com.ssm.service.LogService;
import com.ssm.service.UserService;
import com.ssm.utils.ConstantVar;
import com.ssm.utils.IPAddressUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;

/**
 * 日誌切面
 */
@Aspect
@Component
public class LogAspect {
	@Autowired
	private HttpServletRequest request;

	@Autowired
	private UserService userService;

	// 注入Service用於把日誌保存數據庫
	@Autowired
	private LogService logService;

	private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);

	// Controller層切點
	@Pointcut("@annotation(com.ssm.annotation.Log)") //@annotation用於匹配當前執行方法持有指定註解的方法;
	public void logAspect() {
	}

	/**
	 * 後置通知 用於攔截Controller層記錄用戶的操做
	 *
	 * @param joinPoint
	 *            切點
     * @param rvt
     *            指定一個 returning 屬性,該屬性值爲 rvt , 表示 容許在 加強處理方法中使用名爲rvt的形參,該形參表明目標方法的返回值。
	 */
	@AfterReturning(returning = "rvt", pointcut = "logAspect()")
	public void after(JoinPoint joinPoint, Object rvt) {
		try {
			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 value = "";
			StringBuffer descr = new StringBuffer();
			for (Method method : methods) {
				if (method.getName().equals(methodName)) {
					@SuppressWarnings("rawtypes")
					Class[] clazzs = method.getParameterTypes();
					if (clazzs.length == arguments.length) {
						if(method.getAnnotation(Log.class) != null){ // 若是包含註解@log()
							value = method.getAnnotation(Log.class).value();
							String[] anEntry = method.getAnnotation(Log.class).entry();
							for (String en : anEntry) {
								String[] entry = en.split(",");
								String[] nameArray = entry[0].split("=");
								String val = StringUtils.defaultString(request.getParameter(nameArray[0]), "");
								if (!StringUtils.isBlank(val)) {
									if (entry.length == 2) {
										String[] valueEntry = entry[1].split(";");
										for (String valueArray : valueEntry) {
											String[] vals = valueArray.split("=");
											if (vals[0].equalsIgnoreCase(val)) {
												val = vals[1];
												break;
											}
										}
									}
									descr.append(',');
									descr.append(nameArray[1]);
									descr.append('=');
									descr.append(val);
								}
							}
							if (descr.length() > 0) {
								descr.deleteCharAt(0);
							}
							break;
						}
					}
				}
			}
			OperLog operLog = new OperLog();

			if (request.getRequestURI().contains("/login") && "loginPost".equalsIgnoreCase(joinPoint.getSignature().getName())) {
				// 用戶登陸日誌記錄
				operLog.setUserId(request.getParameter("username"));
				Subject curUser = SecurityUtils.getSubject();
				User loginUser = (User) curUser.getSession().getAttribute(ConstantVar.LOGIN_USER);
				if (loginUser != null) {
					operLog.setUserId(loginUser.getId());
					operLog.setUserName(loginUser.getUserName());
					operLog.setOperStatus(ConstantVar.OPER_LOG_STATUS_SUCCESS);
				} else {
					operLog.setOperStatus(ConstantVar.OPER_LOG_STATUS_FAIL);
				}
			}else if (request.getRequestURI().contains("/logout")
					&& "logout".equalsIgnoreCase(joinPoint.getSignature().getName())) {
				// 退出日誌
				String userId = (String) arguments[0];
				operLog.setUserId(userId);
				
		        User loginUser = userService.findUserByUserId(userId);
				operLog.setUserName(loginUser.getUserName());
			} else {
				Subject curUser = SecurityUtils.getSubject();
				if(curUser.getPrincipal()!=null){
					//從session中獲取當前登陸用戶的User對象
					User loginUser = (User) curUser.getSession().getAttribute(ConstantVar.LOGIN_USER);
					operLog.setUserName(loginUser.getUserName());
					operLog.setUserId(loginUser.getId());
				}

			}
			if(new Integer(operLog.getOperStatus())!=null){
                operLog.setOperStatus(ConstantVar.OPER_LOG_STATUS_SUCCESS);
            }
            operLog.setClientIp(IPAddressUtil.getIpAddress(request));
            operLog.setReqUrl(request.getRequestURI());
            joinPoint.getSignature();
            operLog.setMethod(joinPoint.getSignature().getDeclaringTypeName()+","+joinPoint.getSignature().getName());
            operLog.setOperEvent(value);
            operLog.setLogDesc("該方法實際入參爲:"+descr.toString()); // 描述信息
			// 保存數據庫
			logService.insertLog(operLog);
		} catch (Exception e) {
			// 記錄本地異常日誌
			logger.error("後置通知異常:異常信息:", e.getMessage());
			e.printStackTrace();
		}
	}
}

1.4 spring-mvc.xml:添加aop相關配置

<!-- 啓動對@AspectJ註解的支持 -->
<aop:aspectj-autoproxy proxy-target-class="true"/>

<!-- 日誌註解 -->
<bean id="LogAspect" class="com.ssm.annotation.LogAspect"/>

1.5 IndexController.IndexController:Log註解的實際應用

@Controller
public class IndexController {

	@Log(value = "進入guest", entry = { "parameter1=參數1","parameter2=參數2", })
	@RequestMapping(value = "/guest", method = RequestMethod.GET)
	public String guest(Model model,String parameter1,Integer parameter2) {
		return "guest/guestIndex";
	}
	
}

二、Exception異常統一處理(aop)

2.1ExceptionHandler.java:aop

異常處理,除常規日誌字段外,還將 具體錯誤信息Exception類型該方法實際入參都保存到數據庫中git

package com.ssm.aop;

import com.ssm.annotation.Log;
import com.ssm.model.OperLog;
import com.ssm.model.User;
import com.ssm.service.LogService;
import com.ssm.utils.IPAddressUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.springframework.aop.ThrowsAdvice;
import com.ssm.utils.ConstantVar;
import org.springframework.beans.factory.annotation.Autowired;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.lang.reflect.Method;

/**
 * aop:異常處理
 */
public class ExceptionHandler implements ThrowsAdvice {
    private static final Logger LOG = Logger.getLogger(ExceptionHandler.class);

    @Autowired
    private HttpServletRequest request;

    @Autowired
    private LogService logService;

    public void afterThrowing(JoinPoint joinPoint, Exception e) {
        LOG.error("出現Exception:url爲" + request.getRequestURI() + ";錯誤類型爲"+e.getStackTrace()[0]+"");
        OperLog operLog = new OperLog();
        StringBuffer operEvent = new StringBuffer();
        String descr4Exception = "";   // 具體錯誤信息

        try {
            String targetName = joinPoint.getTarget().getClass().getName(); // 請求類名稱
            String methodName = joinPoint.getSignature().getName(); // 請求方法
            Object[] arguments = joinPoint.getArgs();
            Class<?> targetClass = null;
            targetClass = Class.forName(targetName);

            Method[] methods = targetClass.getMethods();
            for (Method method : methods) {
                if (method.getName().equals(methodName)) {
                    Class[] clazzs = method.getParameterTypes();
                    if (clazzs.length == arguments.length) {
                        if(method.getAnnotation(Log.class) != null){ // 若是包含註解@log()
                            operEvent.append(method.getAnnotation(Log.class).value());
                            operEvent.append("。");
                            break;
                        }
                    }
                }
            }
            operEvent.append("該方法實際入參爲:");
            for (int i = 0; i < joinPoint.getArgs().length; i++) {
                operEvent.append(joinPoint.getArgs()[i]);
                operEvent.append(",");
            }
            operEvent.deleteCharAt(operEvent.length()-1); //刪除最後一個 ","
            operEvent.append("。Exception類型爲:");
            operEvent.append(e.getClass());
            descr4Exception = createExceptionDetail(e);

            Subject curUser = SecurityUtils.getSubject();
            if (request.getRequestURI().contains("/logout")
                    && "logout".equalsIgnoreCase(joinPoint.getSignature().getName())) {
                // 退出日誌
                String userId = (String) arguments[0];
                operLog.setUserId(userId);
            }
            if(curUser.getPrincipal()!=null){
                //從session中獲取當前登陸用戶的User對象
                User loginUser = (User) curUser.getSession().getAttribute(ConstantVar.LOGIN_USER);
                operLog.setUserName(loginUser.getUserName());
                operLog.setUserId(loginUser.getId());
            }
            operLog.setClientIp(IPAddressUtil.getIpAddress(request));
        }catch (ClassNotFoundException e1) {
            e1.printStackTrace();
            LOG.error("實例化失敗:ClassNotFoundException");
        }catch (IOException e2) {
            e2.printStackTrace();
            operLog.setClientIp("未知IP:IOException");
        }

        operLog.setReqUrl(request.getRequestURI());
        operLog.setMethod(joinPoint.getSignature().getDeclaringTypeName()+","+joinPoint.getSignature().getName());
        operLog.setOperEvent((operEvent.toString()).length()>255?(operEvent.toString()).substring(0,255):operEvent.toString());
        operLog.setOperStatus(ConstantVar.OPER_LOG_STATUS_FAIL);
        operLog.setLogDesc("具體Exception信息爲:"+ descr4Exception);
        try{
            // 保存到數據庫
            logService.insertLog(operLog);
        }catch (Exception ex){
            ex.printStackTrace();
            LOG.error("log保存數據庫失敗");
        }
    }

    /**
     * 異常數組轉成字符串
     *
     * @param e
     * @return
     * @author
     * @2016-8-18 下午5:43:20
     */
    private String createExceptionDetail(Exception e) {
        StackTraceElement[] stackTraceArray = e.getStackTrace();
        StringBuilder detail = new StringBuilder();
        for (int i = 0; i < stackTraceArray.length; i++) {
            //255位,此處是考慮數據庫相應字段的大小限制
            if((detail.toString()+stackTraceArray[i]).length() > 255){
                return detail.toString();
            }
            detail.append(stackTraceArray[i] + "\r\n");
        }
        return detail.toString();
    }
}

2.2 spring-mvc.xml:添加aop相關配置

<!-- 日誌註解 -->
<bean id="LogAspect" class="com.ssm.annotation.LogAspect"/>

<!-- 異常捕獲aop -->
<bean id="exceptionHandler" class="com.ssm.aop.ExceptionHandler" />

<aop:config>
	<aop:aspect ref="exceptionHandler">
		<aop:pointcut id="exceptionService" expression="execution(* com.ssm.*.*.*(..))" />
		<aop:after-throwing pointcut-ref="exceptionService" method="afterThrowing" throwing="e"/>
	</aop:aspect>
</aop:config>

3 相關日誌

3.1 爲方便測試,利用guestError方法來拋出異常

@Log(value = "進入guest,此處模擬拋出異常")
@RequestMapping(value = "/guestError", method = RequestMethod.GET)
public String guestError(Model model) {
	LOG.info("進入guest的index");
	if(true) {
		throw new RuntimeException();
	}
	return "guest/guestIndex";
}

3.2 當url爲 http://127.0.0.1:8080/項目名/guestError,數據庫插入數據爲

數據庫異常日誌_1

數據庫異常日誌_2

數據庫異常日誌_3

3.3 未登陸前訪問,與登陸後訪問所留下的日誌記錄

控制檯日誌

數據庫日誌

四、相關參考連接

Spring aop 實現異常攔截 - 塗墨留香 - 博客園github

利用spring aop統一處理異常和打日誌 - Ray的專欄 - 博客頻道 - CSDN.NETspring

基於spring註解AOP的異常處理 - 小眼兒 - 博客園數據庫

相關文章
相關標籤/搜索