最近以爲寫的一點代碼(JAVA),還以爲頗爲自得,貢獻出來供你們參考。java
首先,先上代碼:web
@Controller public class Controller1{ @WriteLog(value = "${p0.username}從${ctx.ip}登陸, 登陸${iif(ret.success,'成功','失敗')}") public Object login(Login loginObj, HttpServletRequest req){ //blablabla... } }
在代碼中,給login方法加上@WriteLog——至關於C#的[WriteLog],當代碼運行了login方法時,spring就會自動記錄日誌——而日誌內容則是會自動替換其中${}的佔位符號。spring
如上就會記錄日誌:「.net bean從127.0.0.1登陸,登陸成功」數據庫
其它地方想要怎麼記日誌,也是如此,比起原來session
public class ClassA{ private static final Log log = LogFactory.getLog(ClassA.class); public void Method(String p1, String p2){ log.info("日誌, 參數p1:"+p1+", 參數p2:"+ p2); //blablabla } }
我我的以爲方便太多了。框架
按照原來的代碼,客戶說要增長日誌,那咱們確定不肯意搞這些事情,可是如今只是加@WriteLog這樣的方式的話,仍是能接受的。jsp
如下,我就將個人實現方法,公知於衆,就算不能使用其中的代碼,也能夠從中獲得一些啓發吧。ide
首先聲明一下,我是用java,基於spring框架——對於.net人員最多的博客園可能直接拷貝代碼怕是不可能了。函數
@Retention(RetentionPolicy.RUNTIME) @Target(value={ElementType.METHOD}) public @interface WriteLog { public String value() default ""; public WriteType type() default WriteType.after; public enum WriteType{ before, after } }
java中的Annotation和.net中的Attribute是有些不一樣的,java中的Annotation能夠聲明爲SOURCE, CLASS, RUNTIME,代表Annotation的保存時效。這裏須要聲明爲RUNTIME。lua
WriteType代表了是在何時記錄日誌,before,只能取到參數的信息,after還能夠取到返回值是什麼。
<bean id="userLogPointcut" class="org.springframework.aop.support.annotation.AnnotationMatchingPointcut"> <constructor-arg index="0" value="org.springframework.stereotype.Controller"></constructor-arg> <constructor-arg index="1" value="annotations.WriteLog"></constructor-arg> </bean>
這個 org.springframework.aop.support.annotation.AnnotationMatchingPointcut 是 Pointcut接口的一個實現。
什麼是Pointcut呢,就是Spring作Aop時,須要在什麼位置進行Aop。
那這個AnnotationMatchingPointcut 就是經過Annotation來進行查找哪些位置須要進行Pointcut。
第一個構造函數參數是代表有@Controller的類,第二個構造函數參數代表有@WriteLog的方法。
<bean id="userLogAdvice" class="WriteLogAdvice"> </bean>
在java中有各類Advice(有before, after, methodInceptor),配置一個bean而後實現Advice,實現以下
public class WriteLogAdvice implements MethodInterceptor { private UserLogService service; @Override public Object invoke(MethodInvocation arg0) throws Throwable { if(service==null){ service = ContextUtil.getBean(UserLogService.class); } if(service != null){ return service.insertUserLog(arg0); } return arg0.proceed(); } }
這裏直接將方法交給service.insertUserLog中進行處理。
<bean id="userLogAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"> <property name="advice" ref="userLogAdvice" /> <property name="pointcut" ref="userLogPointcut" /> </bean>
分別引用上面已經配置好的advice還pointcut。
在上一步中,咱們將日誌記錄的實現交給service.insertUserLog來處理了。
這個日誌記錄實現,須要考慮的是參數信息如何填充到日誌裏,其它把日誌放到哪(放到數據庫,仍是文件),都是簡單的事情。
咱們能夠從 MethodInvocation 對象中取到每一個參數的值——可是不能取到參數名(到了運行時,都是什麼 arg0, arg1之類無心義的參數名了)。
Object[] args = mi.getArguments();
這樣就能取到所有參數的值對象了。
而使用
Object result = mi.proceed();
獲得的就是方法的返回值對象。
從「${p0.username}從${ctx.ip}登陸, 登陸${iif(ret.success,'成功','失敗')}」 這個字符串來看,很像jstl中的代碼,其實正是使用jstl來實現的。
使用jstl的緣由是
1. 這個代碼就是放到web環境中運行的,那麼jstl的jar包確定會在其中
2. p0.username,調用的是p0.getUsername(),這個是jstl默認支持的
固然也可使用其它模板引擎,如freemarker,或者本身實現一個也行——就是比較花時間。
咱們用jstl都是在jsp中使用,那麼在代碼裏怎麼使用呢?這個我也在網上找了好久,都沒有找到,最後只好反編譯jstl的jar來查找一下。
下面是在java代碼中使用jstl的關鍵代碼
/** * 實際執行日誌的寫入 * @param mi 當前調用的函數 * @param annotation WriteLog對象 * @param result 函數返回值 */ private void realWriteLog(final MethodInvocation mi, final WriteLog annotation, final Object result) { try { if (annotation != null) { //聲明一個VariableResolver 用於初始化 Evaluator MapVariableResolver vr = new MapVariableResolver(mi, result); //ELEvaluator 用來 evaluate ELEvaluator eval = new ELEvaluator(vr); //容許包含函數 System.setProperty("javax.servlet.jsp.functions.allowed", "true"); String msg = annotation.value(); if(msg!=null){ //爲了便書寫,WriteLog中的單引號就表示雙引號(否則還須要轉義) msg = msg.replaceAll("'", "\""); //執行evaluate,String.class表示eval返回的類型,fns是函數映射map,fn是函數前綴 Object obj = eval.evaluate(msg, null, String.class, fns, "fn"); //記錄日誌 userLog.info(obj); //插入到數據庫 dao.insert((String)obj); } vr.map.clear(); } } catch (Exception ex) { log.warn("記錄用戶日誌失敗", ex); } }
其中fns這個map對象,使用以下的方法獲得
static Map<String, Method> fns = new HashMap<String, Method>(); static{ try { //此處添加jstl中的默認方法 Method[] methods = Functions.class.getMethods(); for(Method m : methods){ if((m.getModifiers()&Modifier.STATIC)==Modifier.STATIC){ fns.put("fn:"+m.getName(), m); } } //還有一些本身定義的方法,也加入進去 methods = WriteLogFunctions.class.getMethods(); for(Method m : methods){ if((m.getModifiers()&Modifier.STATIC)==Modifier.STATIC){ fns.put("fn:"+m.getName(), m); } } } catch (SecurityException e) { e.printStackTrace(); } }
其中 MapVariableResolver 就是將方法的參數值放入到map中,要取的時候就從map中取出來。我這裏是將該類的實現直接放到內部類來實現
private class MapVariableResolver implements VariableResolver{ private Map<String, Object> map; public MapVariableResolver(MethodInvocation mi, Object result){ Object[] args = mi.getArguments(); map = new LinkedHashMap<String, Object>(args.length+2); for(int i=0; i<args.length; i++){ map.put("p"+i, args[i]); if((!map.containsKey("ctx")) && args[i] instanceof HttpServletRequest){ map.put("ctx", new LogContextImpl((HttpServletRequest)args[i])); } } map.put("ret", result); } @Override public Object resolveVariable(String arg0, Object arg1) throws ELException { if(map.containsKey(arg0)){ return map.get(arg0); } return "[no named("+arg0+") value]"; } } }
使用斜體的3行,增長了一個 LogContextImpl 對象,主要用於取HttpServletRequest對象裏的session,以及ip。
private class LogContextImpl implements LogContext{ private Map session; private String ip; public LogContextImpl(HttpServletRequest req){ HttpSession session2 = req.getSession(); if(session2 instanceof Map){ session = (Map) session2; }else{ session = new HashMap(); Enumeration names = session2.getAttributeNames(); while(names.hasMoreElements()){ String next = (String)names.nextElement(); session.put(next, session2.getAttribute(next)); } } ip = req.getHeader("x-forwarded-for"); if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = req.getHeader("Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = req.getHeader("WL-Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = req.getRemoteAddr(); } } public String getIp() { return ip; } public Map getSession(){ return session; } }
public interface LogContext { @SuppressWarnings("rawtypes") public Map getSession(); public String getIp(); }
至此,這個WriteLog的實現就所有完成了。