在通常系统中,当咱们作了一些重要的操做时,如登录系统,添加用户,删除用户等操做时,咱们须要将这些行为持久化。本文咱们经过Spring AOP和Java的自定义注解来实现日志的插入。此方案对原有业务入侵较低,实现较灵活java
咱们将日志抽象为如下两个类:功能模块和操做类型 使用枚举类定义功能模块类型ModuleType,如学生、用户模块git
public enum ModuleType {
DEFAULT("1"), // 默认值
STUDENT("2"),// 学生模块
TEACHER("3"); // 用户模块
private ModuleType(String index){
this.module = index;
}
private String module;
public String getModule(){
return this.module;
}
}
复制代码
使用枚举类定义操做的类型:EventType。如登录、添加、删除、更新、删除等github
public enum EventType {
DEFAULT("1", "default"), ADD("2", "add"), UPDATE("3", "update"), DELETE_SINGLE("4", "delete-single"),
LOGIN("10","login"),LOGIN_OUT("11","login_out");
private EventType(String index, String name){
this.name = name;
this.event = index;
}
private String event;
private String name;
public String getEvent(){
return this.event;
}
public String getName() {
return name;
}
}
复制代码
这里咱们定义日志的开关量,类上只有这个值为true,这个类中日志功能才开启redis
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface LogEnable {
/**
* 若是为true,则类下面的LogEvent启做用,不然忽略
* @return
*/
boolean logEnable() default true;
}
复制代码
这里定义日志的详细内容。若是此注解注解在类上,则这个参数作为类所有方法的默认值。若是注解在方法上,则只对这个方法启做用spring
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({java.lang.annotation.ElementType.METHOD, ElementType.TYPE})
public @interface LogEvent {
ModuleType module() default ModuleType.DEFAULT; // 日志所属的模块
EventType event() default EventType.DEFAULT; // 日志事件类型
String desc() default ""; // 描述信息
}
复制代码
此注解若是注解在方法上,则整个方法的参数以json的格式保存到日志中。若是此注解同时注解在方法和类上,则方法上的注解会覆盖类上的值。数据库
@Target({ElementType.FIELD,ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogKey {
String keyName() default ""; // key的名称
boolean isUserId() default false; // 此字段是不是本次操做的userId,这里略
boolean isLog() default true; // 是否加入到日志中
}
复制代码
定义保存日志信息的类json
public class LogAdmModel {
private Long id;
private String userId; // 操做用户
private String userName;
private String admModel; // 模块
private String admEvent; // 操做
private Date createDate; // 操做内容
private String admOptContent; // 操做内容
private String desc; // 备注
set/get略
}
复制代码
定义日志处理的接口类ILogManager 咱们能够将日志存入数据库,也能够将日志发送到开中间件,若是redis, mq等等。每一种日志处理类都是此接口的实现类bash
public interface ILogManager {
/**
* 日志处理模块
* @param paramLogAdmBean
*/
void dealLog(LogAdmModel paramLogAdmBean);
}
复制代码
ILogManager实现类,将日志入库。这里只模拟入库mvc
@Service
public class DBLogManager implements ILogManager {
@Override
public void dealLog(LogAdmModel paramLogAdmBean) {
System.out.println("将日志存入数据库,日志内容以下: " + JSON.toJSONString(paramLogAdmBean));
}
}
复制代码
@Component
@Aspect
public class LogAspect {
@Autowired
private LogInfoGeneration logInfoGeneration;
@Autowired
private ILogManager logManager;
@Pointcut("execution(* com.hry.spring.mvc.aop.log.service..*.*(..))")
public void managerLogPoint() {
}
@Around("managerLogPoint()")
public Object aroundManagerLogPoint(ProceedingJoinPoint jp) throws Throwable {
….
}
}
复制代码
aroundManagerLogPoint:主方法的主要业务流程 1. 检查拦截方法的类是否被@LogEnable注解,若是是,则走日志逻辑,不然执行正常的逻辑 2. 检查拦截方法是否被@LogEvent,若是是,则走日志逻辑,不然执行正常的逻辑 3. 根据获取方法上获取@LogEvent 中值,生成日志的部分参数。其中定义在类上@LogEvent 的值作为默认值 4. 调用logInfoGeneration的processingManagerLogMessage填充日志中其它的参数,作个方法咱们后面再讲 5. 执行正常的业务调用 6. 若是执行成功,则logManager执行日志的处理(咱们这里只记录执行成功的日志,你也能够定义记录失败的日志)ide
@Around("managerLogPoint()")
public Object aroundManagerLogPoint(ProceedingJoinPoint jp) throws Throwable {
Class target = jp.getTarget().getClass();
// 获取LogEnable
LogEnable logEnable = (LogEnable) target.getAnnotation(LogEnable.class);
if(logEnable == null || !logEnable.logEnable()){
return jp.proceed();
}
// 获取类上的LogEvent作为默认值
LogEvent logEventClass = (LogEvent) target.getAnnotation(LogEvent.class);
Method method = getInvokedMethod(jp);
if(method == null){
return jp.proceed();
}
// 获取方法上的LogEvent
LogEvent logEventMethod = method.getAnnotation(LogEvent.class);
if(logEventMethod == null){
return jp.proceed();
}
String optEvent = logEventMethod.event().getEvent();
String optModel = logEventMethod.module().getModule();
String desc = logEventMethod.desc();
if(logEventClass != null){
// 若是方法上的值为默认值,则使用全局的值进行替换
optEvent = optEvent.equals(EventType.DEFAULT) ? logEventClass.event().getEvent() : optEvent;
optModel = optModel.equals(ModuleType.DEFAULT) ? logEventClass.module().getModule() : optModel;
}
LogAdmModel logBean = new LogAdmModel();
logBean.setAdmModel(optModel);
logBean.setAdmEvent(optEvent);
logBean.setDesc(desc);
logBean.setCreateDate(new Date());
logInfoGeneration.processingManagerLogMessage(jp,
logBean, method);
Object returnObj = jp.proceed();
if(optEvent.equals(EventType.LOGIN)){
//TODO 若是是登陆,还须要根据返回值进行判断是否是成功了,若是成功了,则执行添加日志。这里判断比较简单
if(returnObj != null) {
this.logManager.dealLog(logBean);
}
}else {
this.logManager.dealLog(logBean);
}
return returnObj;
}
/**
* 获取请求方法
*
* @param jp
* @return
*/
public Method getInvokedMethod(JoinPoint jp) {
// 调用方法的参数
List classList = new ArrayList();
for (Object obj : jp.getArgs()) {
classList.add(obj.getClass());
}
Class[] argsCls = (Class[]) classList.toArray(new Class[0]);
// 被调用方法名称
String methodName = jp.getSignature().getName();
Method method = null;
try {
method = jp.getTarget().getClass().getMethod(methodName, argsCls);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
return method;
}
}
复制代码
这里咱们模拟学生操做的业务,并使用上文注解应用到上面并拦截日志
业务接口类,执行通常的CRUD
public interface IStudentService {
void deleteById(String id, String a);
int save(StudentModel studentModel);
void update(StudentModel studentModel);
void queryById(String id);
}
复制代码
@Service
@LogEnable // 启动日志拦截
@LogEvent(module = ModuleType.STUDENT)
public class StudentServiceImpl implements IStudentService {
@Override
@LogEvent(event = EventType.DELETE_SINGLE, desc = "删除记录") // 添加日志标识
public void deleteById(@LogKey(keyName = "id") String id, String a) {
System.out.printf(this.getClass() + "deleteById id = " + id);
}
@Override
@LogEvent(event = EventType.ADD, desc = "保存记录") // 添加日志标识
public int save(StudentModel studentModel) {
System.out.printf(this.getClass() + "save save = " + JSON.toJSONString(studentModel));
return 1;
}
@Override
@LogEvent(event = EventType.UPDATE, desc = "更新记录") // 添加日志标识
public void update(StudentModel studentModel) {
System.out.printf(this.getClass() + "save update = " + JSON.toJSONString(studentModel));
}
// 没有日志标识
@Override
public void queryById(String id) {
System.out.printf(this.getClass() + "queryById id = " + id);
}
}
复制代码
执行测试类,打印以下信息,说明咱们日志注解配置启做用了:
将日志存入数据库,日志内容以下: {"admEvent":"4","admModel":"1","admOptContent":"{\"id\":\"1\"}","createDate":1525779738111,"desc":"删除记录"}
复制代码
以上的详细的代码见下面 github代码,请尽可能使用tag v0.21,不要使用master,由于我不能保证master代码一直不变