一、說明
近期,心中萌發了做一個個人網站的想法,來一場說走就走的程式設計之旅。說做就做,在專案框架搭建(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引用方法傳回值 2.2 設定cglib代理<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中文網其他相關文章!