系统业务操作日志记录是每个系统必不可少的一部分,但通常的做法是在每个需要记录日志的地方,调用添加日志的Service方法,这样做主要是显的麻烦。
我们可以使用Spring AOP结合注解来实现这一功能。

1、首先定义一个注解类,如下:

1
2
3
4
5
6
7
@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysLog {
String module() default "";
String desc() default "";
}

注解类有2个属性,module是操作的模块,desc为具体记录的日志信息。

2、定义切面类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Method;
/**
* Created by Administrator on 2017/6/2.
*/
@Aspect
public class LogAspect {
private final Logger logger = LoggerFactory.getLogger(LogAspect.class);
private long timeStart;
@Before(value = "execution(* aop.service..*.*(..))")
public void doBefore(JoinPoint joinPoint) {
timeStart = System.currentTimeMillis();
}
@After(value = "execution(* aop.service..*.*(..))")
public void doAfter(JoinPoint joinPoint) {
long timeEnd = System.currentTimeMillis();
logger.info("方法:" + joinPoint.getTarget().getClass().getSimpleName()+"."+joinPoint.getSignature().getName() + "执行结束。耗时:" + (timeEnd-timeStart)+"ms.");
Method proxyMethod = ((MethodSignature)(joinPoint.getSignature())).getMethod();
try {
Method sourceMethod = joinPoint.getTarget().getClass().getMethod(proxyMethod.getName(), proxyMethod.getParameterTypes());
SysLog sysLog = sourceMethod.getAnnotation(SysLog.class);
if (sysLog != null) {
String module = sysLog.module();
String desc = sysLog.desc();
// 这里获取登陆用户信息
/**
* 1.获取request信息
* 2.根据request获取session
* 3.从session中取出登录用户信息
*/
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes)ra;
HttpServletRequest request = sra.getRequest();
HttpSession session = request.getSession();
// 从session中获取用户信息
String loginInfo = (String) session.getAttribute("username");
String username = "admin";
logger.info(username + "在[" + module + "]" + desc);
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}

3、spring配置文件加入:

1
2
<aop:aspectj-autoproxy proxy-target-class="true" />
<bean id="aspectEventLog" class="aop.LogAspect" />

4、maven依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.1</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.12</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
</dependency>
<dependency>
<groupId>${spring.groupId}</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>

5、测试service:

1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class TestService {
@SysLog(module = SysConstant.MODULE_USER_MGR,desc = "添加用户")
public void test() {
System.out.println("执行了test()方法,username=" + username);
try {
Thread.sleep((long) (Math.random()*3000L));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

执行结果:

1
2
3
执行了test()方法
12:39:30,355 INFO main aop.LogAspect:33 - 方法:TestService.test执行结束。耗时:1601ms.
12:39:30,356 INFO main aop.LogAspect:44 - admin在[系统管理]添加用户

最后说明一下,这种事没法很具体的,比如某个人添加了某个用户,这种级别做不到。
上面拿到httpservletrequest后当然可以拿到所有的请求参数,如果要更具体,就需要在每个请求上携带特定的参数。

比如用户AA添加了用户BB,BB就得放到固定名字的参数中。如果不用注解,就是把模块名和具体的日志信息都放到request中,在切面中从request获取,不过这样就比较麻烦了。

如果只是需要具体到模块,并记录IP之类的,上面的就可以满足了。