首頁  >  文章  >  Java  >  java開發實戰之springMVC使用AOP實作存取日誌的管理

java開發實戰之springMVC使用AOP實作存取日誌的管理

无忌哥哥
无忌哥哥原創
2018-07-20 10:51:102927瀏覽

一、說明

近期,心中萌發了做一個個人網站的想法,來一場說走就走的程式設計之旅。說做就做,在專案框架搭建(SpringMVC mybatis mysql)好了之後,開始考慮專案中日誌的設計。經過考慮並結合網路上的資料,決定採用註解的方式來記錄訪問日誌。當然,目前的日誌設計還不夠完美,後期會在開發的過程中逐漸完善。

二、實作

2.1 關於AOP及相關註解

相對於AOP,有很多人偏向使用攔截器來管理日誌,這點要看個人的想法了。那麼如何實現AOP攔截controller呢?由於預設的情況下,controller是交給jdk去代理的,因此,要想AOP能夠攔截到controller,必須將其指定給cglib代理。

下面介紹一下,使用AOP攔截controller用到的註解(標紅字段代表將會使用),當然,我們也可以使用配置文件的方式去定義,但是個人更喜歡將模組集中在一起,找檔案真的很累~

         @Target:註解的作用目標,也就是註解會對哪些物件產生作用。包括:

                           ElementType.TYPE           、枚舉、註解

                           ElementType.FIELD         、列舉的常數

                           

#                           ##                           ElementType.CONSTRUCTOR                      ElementType. LOCAL_VARIABLE                       局部變數

                   註解

                           ElementType.PACKAGE        #          @Retention:註解的保留位置,用於描述註解的生命週期,通俗的講,@ Retention註解負責定義該註解在什麼範圍內或條件下才會去產生作用。

                           RetentionPolicy.SOURCE           節碼檔案不包含

                           RetentionPolicy.CLASS         名詞化 存在但在運作時無法取得

                           

#RetentionPolicy.RUNTIME    可以透過反射取得到

#           @Document:說明此註解將包含在javadoc中

           以上註解,上加為

@Inherited、@Repeatabled#d # 註解,稱為java中的元註解。什麼是元註解?

           元註解的功能是負責註解其他註解。 Java5.0定義了4個標準的meta-annotation類型,它們被用來提供對其它 annotation類型作說明。

           關於註解及元註解的解釋,請點擊這裡

           @Aspect:當@Aspect宣告與類別上時,表示這個類別將會作為一個切面,即切面類別。此時容器就可以讀到這個類,但前提是開啟了cglib代理。

           @Component:將類別宣告為bean並注入到容器中,spring在啟動時會掃描並載入。與在設定檔中定義bean的效果相同。

           @Pointcut:方法層級的註解,使用此註解後,可被其他方法引用。

            與@Aspect、@Pointcut一起使用的還有5種通知型註解,也稱為增強註解:

##             #                  前置通知,在方法執行前執行

                              @After                       @AfterReturning     後置【try】通知,放在方法頭上,並使用returning引用方法傳回值

                              @AfterThrowing      後置【catch】 放在方法        @Around                環繞通知,並放在方法頭上,以這個方式決定真實的方法是否執行,而且必須有回傳值

2.2 設定cglib代理

在spring-mvc.xml檔中加入如下程式碼:

<aop:aspectj-autoproxy proxy-target-class="true" />

2.3 自訂註解,用於描述日誌資訊

一般我們在建立自訂註解時,使用@interface會使此類預設繼承annotation。程式碼如下:

package com.t1heluosh1.system.log.annotion;

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层注解
 * 
 * @author xuyong
 *
 */
//作用于参数和方法上
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysControllerLog {

	int logType() default 100;				//日志类型,默认为100-系统
	
	int module() default 100;				//操作模块,默认为100-登录
	
	String description() default "";		//操作描述
}

自訂註解建立後,我們需要將註解作為bean注入到容器中,在spring-mvc.xml檔案中加入如下程式碼:

<context:component-scan base-package="com.t1heluosh1.system.log" >
         <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
#2.4定義切面類,實作記錄日誌的功能

這裡沒有什麼可以多說的,直接看程式碼:

package com.t1heluosh1.system.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.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import com.t1heluosh1.pullulate.biz.sysLog.service.SysLogService;
import com.t1heluosh1.pullulate.biz.sysLog.vo.SysLogVo;
import com.t1heluosh1.pullulate.biz.user.model.User;
import com.t1heluosh1.system.constant.SysParams;
import com.t1heluosh1.system.log.annotion.SysControllerLog;
import com.t1heluosh1.util.IPUtil;


/**
 * 日志切点类即实现类
 * 
 * @author xuyong
 *
 */
@Aspect  
@Component 
public class SysLogAspect {

	//本地异常日志记录对象
	private static final Logger logger = Logger.getLogger(SysLogAspect.class);
	
	@Resource
	private SysLogService logService;
	
	
	//切入点定义:controller
	@Pointcut("@annotation(com.t1heluosh1.system.log.annotion.SysControllerLog)")
	public void controllerAspect() {
		System.out.println("---------------------controllerAspect for log start----------------------------");
	}
	
	
	
	/**
	 * controller切入点方法实现
	 * 
	 * @param joinPoint
	 */
	@Before("controllerAspect()")
	public void doBefore(JoinPoint joinPoint) {
		 HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder  
	                .getRequestAttributes()).getRequest();
		 
		 //获取登录用户的信息
		 User user = (User)request.getSession().getAttribute(SysParams.CURRENT_USER);
		 
		 //获取请求IP地址
		 String ip = IPUtil.getRemoteHost(request);
		 
		 try {
			 String methodDesc = getControllerMethodDescription(joinPoint);
			 System.out.println("request method : " + joinPoint.getTarget().getClass().getName()+"."+joinPoint.getSignature().getName()+"()");
			 System.out.println("method description : " + methodDesc);
			 System.out.println("request username : " + (user==null?"no login info found":user.getUserName()));
			 
			 System.out.println("request ip address : "+ ip);
			 
			 System.out.println("insert log infos into db start ...");
			 
			 //获取相关日志参数
			 Object[] orgs = joinPoint.getArgs();
			 SysLogVo sysLogVo = null;
			 if (orgs != null && orgs.length > 0) {
				 for (Object obj:orgs) {
					 if (obj instanceof SysLogVo)
						 sysLogVo = (SysLogVo)obj;
					 
				 }
			 }
			 if (sysLogVo == null) {
				 sysLogVo = new SysLogVo();
			 }
			 //执行日志入库操作
			 
			 //获取注解的信息
			 MethodSignature ms = (MethodSignature)joinPoint.getSignature();
			 Method method = ms.getMethod();
			 SysControllerLog log = method.getAnnotation(SysControllerLog.class);
			 sysLogVo.setLogType(log.logType());
			 sysLogVo.setModule(log.module());
			 sysLogVo.setIpAddr(ip);
			 sysLogVo.setUrl(request.getRequestURI());
			 sysLogVo.setMethodName(joinPoint.getTarget().getClass().getName()+"."+joinPoint.getSignature().getName()+"()");
			 sysLogVo.setMethodDesc(methodDesc);
			 //TODO:remark可根据业务来进行改变,暂时为方法描述
			 sysLogVo.setRemark(log.description());
			 Date date = new Date();
			 sysLogVo.setAddTime(date);
			 sysLogVo.setAddUser(user==null?SysParams.ADMIN_ID:String.valueOf(user.getId()));
			 sysLogVo.setUpdateTime(date);
			 sysLogVo.setUpdateUser(user==null?SysParams.ADMIN_ID:String.valueOf(user.getId()));
			 logService.save(sysLogVo);
			 System.out.println("insert log infos into db successful.");
		 } catch (Exception e) {
			 logger.error("--------------controllerAspect for log fail-----------------------");  
	         logger.error("exception info : ", e);  
		 }
		 
	}
	
	 
	/**
	 * 获取方法的描述
	 * 
	 * @param joinPoint
	 * @return
	 * @throws Exception 
	 */
	@SuppressWarnings("rawtypes")
	private 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(SysControllerLog.class).description();  
        }  
        return description;  
	}
	
	
}

2.5 使用demo

具體使用的方法如下:

/**
	 * 跳转到登陆页面
	 * 
	 * @param request
	 * @return
	 * @throws Exception 
	 */
	@RequestMapping(value="login")
	@SysControllerLog(description="跳转到登录页面",logType=100,module=100)
	public ModelAndView gotoLogin(HttpServletRequest request) {
		ModelAndView modelAndView = new ModelAndView("/show/login");
		return modelAndView;
	}

當使用者重新整理頁面時,控制台會列印相關的存取資訊並將這些資訊入庫。當然,日誌的使用需要根據項目來決定,每個方法前都加入註解,一是影響系統的效能,且使得存取日誌的效果大打折扣;二是這種方式記錄日誌,對於程式碼還是有一定的侵入性的。最後來看看資料庫記錄的資訊:

以上是java開發實戰之springMVC使用AOP實作存取日誌的管理的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn