在Spring中轻松写日志
最近觉得写的一点代码(JAVA),还觉得颇为自得,贡献出来供大家参考。
首先,先上代码:
@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就会自动记录日志——而日志内容则是会自动替换其中${}的占位符号。
如上就会记录日志:“.net bean从127.0.0.1登录,登录成功”
其它地方想要怎么记日志,也是如此,比起原来
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这样的方式的话,还是能接受的。
以下,我就将我的实现方法,公知于众,就算不能使用其中的代码,也可以从中得到一些启发吧。
首先声明一下,我是用java,基于spring框架——对于.net人员最多的博客园可能直接拷贝代码怕是不可能了。
第1步,声明@WriteLog(java中叫Annotation, .net里叫Attribute)
@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。
WriteType表明了是在什么时候记录日志,before,只能取到参数的信息,after还可以取到返回值是什么。
第2步,配置Spring Aop注入
在spring的主配置文件里,需要配置织入点(Pointcut)
<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中进行处理。
Spring接收Aop的bean是Advisor,所以还需要配置Advisor
<bean id="userLogAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="advice" ref="userLogAdvice" />
<property name="pointcut" ref="userLogPointcut" />
</bean>
分别引用上面已经配置好的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的关键代码
/**
* 实际执行日志的写入
* @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的实现就全部完成了。
在Spring中轻松写日志的更多相关文章
- SSM框架中添加写日志功能
前提:要导入log4j的jar包 在web.xml中输入: <!--日志加载--> <context-param> <param-name>log4jConfigL ...
- Spring 中配置log4j日志功能
一,添加log4j依赖包 可从官网上下载该依赖包log4j-x.x.xx.jar,下载后 build path,添加依赖包 二,创建 log4j.properties 配置文件 log4j.prope ...
- 在crontab中动态写日志
45 3 * * * setsid script -c /home/dlht/shell/coreBusiness/coreOpt.sh >> /home/dlht/logs/coreO ...
- Spring Boot 2 中的默认日志管理与 Logback 配置详解
Spring Boot在所有内部日志中使用Commons Logging,但是对底层日志的实现是开放的.在Spring Boot生态中,为Java Util Logging .Log4J2 和Logb ...
- Spring 使用 SLF4J代替 Commons Logging 写日志 异常
项目的日志更换成slf4j和logback后,发现项目无法启动.错误提示 Caused by: java.lang.NoClassDefFoundError: Lorg/apache/commons/ ...
- Spring AOP 实现写事件日志功能
什么是AOP?AOP使用场景?AOP相关概念?Spring AOP组件?如何使用Spring AOP?等等这些问题请参考博文:Spring AOP 实现原理 下面重点介绍如何写事件日志功能,把日志保存 ...
- Spring中操作日志记录web请求的body报文
在spring中,通常可以使用切面编程方式对web请求记录操作日志.但是这种方式存在一个问题,那就是只能记录url中的请求参数,无法记录POST或者PUT请求的报文体,因为报文体是放在request对 ...
- 轻松了解Spring中的控制反转和依赖注入(二)
紧接上一篇文章<轻松了解Spring中的控制反转和依赖注入>讲解了SpringIOC和DI的基本概念,这篇文章我们模拟一下SpringIOC的工作机制,使我们更加深刻的理解其中的工作. 类 ...
- kettle作业中的js如何写日志文件
在kettle作业中JavaScript脚本有时候也扮演非常重要的角色,此时我们希望有一些日志记录.下面是job中JavaScript记录日志的方式. job的js写日志的方法. 得到日志输出实例 o ...
随机推荐
- 用.htaccess文件实现URL重写
注:第一部分来自 http://www.cnblogs.com/wangkongming/archive/2012/11/13/2768251.html 这位博主的个人网站简洁 还有诗歌 ...
- .net 面试基础题
Reference Link:http://www.yjbys.com/bbs/326026.html const关键字用来声明编译时常量,readonly用来声明运行时常量 密封类不能同时为抽象类 ...
- 在_vimrc中 set noexpandtab python 不起效果
我ctm,今天配置不让tab转为空格,在_vimrc中set noexpandtab 不起效果. set ts=4也不起效果. 但是在命令行中其效果. 我都不知道咋办了. 问人说我有可能使用的不是那个 ...
- eclipse中ctrl+h默认打开是JavaSearch,怎么设置成默认打开是FileSearch
window->preferences->General->keys. 找到File Search(有搜索框的,可以搜索),然后在下方 Binding按下ctrl +h .
- 在CentOS 7 中 安装 VSFTP
在线安装:yum install -y vsftpd 使用yum 进行卸载:yum -y remove vsftpd 编辑配置:vi /etc/vsftpd/vsftpd.conf 查看FTP进程是否 ...
- 设计模式--代理模式Proxy(结构型)
一.代理模式 为其他对象提供一种代理以控制对这个对象的访问. 代理模式分为四种: 远程代理:为了一个对象在不同的地址空间提供局部代表.这样可以隐藏一个对象存在于不同地址空间的事实. 虚拟代理:根据需要 ...
- B+Tree和MySQL索引分析
首先区分两组概念: 稠密索引,稀疏索引: 聚簇索引,非聚簇索引: btree和mysql的分析: 参见 http://blog.csdn.net/hguisu/article/details/7786 ...
- Jquary入门( 修改内容)
1. 使用JQ时需要先引用 JQ 包: 其他的JQ代码 需要写在 引用标签的下面如下图[基本格式] JQ中 是纯代码 没有判断 没有循环 如果 有 时间间隔和延迟 则使用JS 代码 详见 下面例 ...
- 如何在Linux中搭建禅道8.4.1(httpd+php+mysql)
1.安装httpd 命令:yum install httpd 然后一路y即可 2.安装php 命令:yum install php 3.安装php-mysql 命令:yum install php ...
- css清除浮动大全共8种方法
原文链接http://www.jb51.net/css/173023.html 清除浮动是每一个 web前台设计师必须掌握的机能.css清除浮动大全,共8种方法. 浮动会使当前标签产生向上浮的效果,同 ...