java writelog_在Spring中轻松写日志

最近觉得写的一点代码(JAVA),还觉得颇为自得,贡献出来供大家参考。

首先,先上代码:

@Controllerpublic classController1{

@WriteLog(value= "${p0.username}从${ctx.ip}登录, 登录${iif(ret.success,'成功','失败')}")publicObject login(Login loginObj, HttpServletRequest req){//blablabla...

}

}

在代码中,给login方法加上@WriteLog——相当于C#的[WriteLog],当代码运行了login方法时,spring就会自动记录日志——而日志内容则是会自动替换其中${}的占位符号。

如上就会记录日志:“.net bean从127.0.0.1登录,登录成功”

其它地方想要怎么记日志,也是如此,比起原来

public classClassA{private static final Log log = LogFactory.getLog(ClassA.class);public voidMethod(String p1, String p2){

log.info("日志, 参数p1:"+p1+", 参数p2:"+p2);//blablabla

}

}

我个人觉得方便太多了。

按照原来的代码,客户说要增加日志,那我们肯定不愿意搞这些事情,但是现在只是加@WriteLog这样的方式的话,还是能接受的。

以下,我就将我的实现方法,公知于众,就算不能使用其中的代码,也可以从中得到一些启发吧。

首先声明一下,我是用java,基于spring框架——对于.net人员最多的博客园可能直接拷贝代码怕是不可能了。

第1步,声明@WriteLog(java中叫Annotation, .net里叫Attribute)

@Retention(RetentionPolicy.RUNTIME)

@Target(value={ElementType.METHOD})public @interfaceWriteLog {public String value() default "";public WriteType type() defaultWriteType.after;public enumWriteType{

before,

after

}

}

java中的Annotation和.net中的Attribute是有些不同的,java中的Annotation可以声明为SOURCE, CLASS, RUNTIME,表明Annotation的保存时效。这里需要声明为RUNTIME。

WriteType表明了是在什么时候记录日志,before,只能取到参数的信息,after还可以取到返回值是什么。

第2步,配置Spring Aop注入

在spring的主配置文件里,需要配置织入点(Pointcut)

这个 org.springframework.aop.support.annotation.AnnotationMatchingPointcut 是 Pointcut接口的一个实现。

什么是Pointcut呢,就是Spring做Aop时,需要在什么位置进行Aop。

那这个AnnotationMatchingPointcut 就是通过Annotation来进行查找哪些位置需要进行Pointcut。

第一个构造函数参数是表明有@Controller的类,第二个构造函数参数表明有@WriteLog的方法。

这只是配置了织入点,还需要告诉怎么处理这个织入点

在java中有各种Advice(有before, after, methodInceptor),配置一个bean然后实现Advice,实现如下

public class WriteLogAdvice implementsMethodInterceptor {privateUserLogService service;

@Overridepublic Object invoke(MethodInvocation arg0) throwsThrowable {if(service==null){

service= ContextUtil.getBean(UserLogService.class);

}if(service != null){returnservice.insertUserLog(arg0);

}returnarg0.proceed();

}

}

这里直接将方法交给service.insertUserLog中进行处理。

Spring接收Aop的bean是Advisor,所以还需要配置Advisor

分别引用上面已经配置好的advice还pointcut。

第3步,进行日志记录的实现

在上一步中,我们将日志记录的实现交给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的关键代码

/*** 实际执行日志的写入

*@parammi 当前调用的函数

*@paramannotation WriteLog对象

*@paramresult 函数返回值*/

private void realWriteLog(finalMethodInvocation mi,final WriteLog annotation, finalObject result) {try{if (annotation != null) {//声明一个VariableResolver 用于初始化 Evaluator

MapVariableResolver vr = newMapVariableResolver(mi, result);//ELEvaluator 用来 evaluate

ELEvaluator eval = newELEvaluator(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 fns = new HashMap();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 implementsVariableResolver{private Mapmap;publicMapVariableResolver(MethodInvocation mi, Object result){

Object[] args=mi.getArguments();

map= new LinkedHashMap(args.length+2);for(int i=0; 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);

}

@OverridepublicObject resolveVariable(String arg0, Object arg1)throwsELException {if(map.containsKey(arg0)){returnmap.get(arg0);

}return "[no named("+arg0+") value]";

}

}

}

使用斜体的3行,增加了一个 LogContextImpl对象,主要用于取HttpServletRequest对象里的session,以及ip。

private class LogContextImpl implementsLogContext{privateMap session;privateString ip;publicLogContextImpl(HttpServletRequest req){

HttpSession session2=req.getSession();if(session2 instanceofMap){

session=(Map) session2;

}else{

session= newHashMap();

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();

}

}publicString getIp() {returnip;

}publicMap getSession(){returnsession;

}

}

public interfaceLogContext {

@SuppressWarnings("rawtypes")publicMap getSession();publicString getIp();

}

至此,这个WriteLog的实现就全部完成了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值