我们知道,在一个系统中,业务操作日志对我们很重要。那么以往我们更多的时候是写个方法,然后哪个模块需要加入日志,就引入这个接口。但是这种其实冗余了很多代码。今天我简单给大家介绍下采用spring AOP来统一帮我们处理业务操作日志。
首先看下我的日志表的设计:
DROP TABLE IF EXISTS `t_log`;
CREATE TABLE `t_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(255) DEFAULT NULL COMMENT '用户名',
`create_date` varchar(20) DEFAULT NULL COMMENT '发生日期',
`module_name` varchar(2000) DEFAULT '' COMMENT '功能模块',
`operation` varchar(255) DEFAULT NULL COMMENT '用户所做的操作',
`work_time` int(11) DEFAULT NULL COMMENT '耗时',
`oper_result` varchar(255) DEFAULT NULL COMMENT '操作结果',
`user_ip` varchar(255) DEFAULT NULL COMMENT '用户ip',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
接下来,创建我们的日志实体类:
public class Log implements Serializable {
private Long id;//自增id
private String userName;//用户名
private String createdate;//日期
private String moduleName;//模块内容
private String operation;//操作(主要是"添加"、"修改"、"删除")
private Long workTime;//耗时(s单位)
private String operResult;//操作结果
private String userIp;//用户ip
//此处省略getter,setter
日志dao:
@Repository("logDao")
public class LogDaoImpl extends BaseDao implements LogDao {
@Override
public Log insert(final Log log) {
final String sql = "insert into t_log(username,create_date,module_name,operation,work_time,oper_result,user_ip) values(?,?,?,?,?,?,?)";
KeyHolder keyHolder = new GeneratedKeyHolder();
super.getJdbcTemplate().update(new PreparedStatementCreator() {
@Override
public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
PreparedStatement ps = connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
ps.setObject(1, log.getUserName());
ps.setObject(2, log.getCreatedate());
ps.setObject(3, log.getModuleName());
ps.setObject(4, log.getOperation());
ps.setObject(5, log.getWorkTime());
ps.setObject(6, log.getOperResult());
ps.setObject(7, log.getUserIp());
return ps;
}
}, keyHolder);
log.setId(keyHolder.getKey().longValue());
return log;
}
}
日志service:
@Transactional(propagation = Propagation.REQUIRED)
@Service("logService")
public class LogServiceImpl implements LogService {
@Autowired
private LogDao logDao;
@Override
public Log insert(final Log log) {
return logDao.insert(log);
}
}
接下来,开始我们的切面编程,首先创建aop包,并在包里面创建LogAspect切面类,由于这个只说Aop实现日志记录,并不会给大家讲解具体的aop知识,如果对aop不懂的同学请自行查阅资料学习。
我这里采用的通知类型为环绕通知。下面直接上代码:
@Component
@Aspect
public class LogAspect {
@Autowired
private LogService logService;//日志记录Service
/**
* 方法切入点
*/
@Pointcut("execution(* com.xwtec.manager.web.controller.*.*(..))")
public void pointerCutMethod() {
}
/**
* 环绕通知
*
* @param joinPoint
* @param annotation
* @return
*/
@Around(value = "pointerCutMethod() && @annotation(annotation)")
public Object doAround(ProceedingJoinPoint joinPoint, OperAnnotation annotation) {
Log log = new Log();
//通过注解获取当前属于那个模块
log.setModuleName(annotation.moduleName());
//通过注解获取当前的操作方式
log.setOperation(annotation.option());
log.setCreatedate(DateUtils.getCurdateStr("yyyy-MM-dd HH:mm:ss"));
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
Long beginTime = System.currentTimeMillis();
if (ra != null) {
ServletRequestAttributes sra = (ServletRequestAttributes) ra;
HttpServletRequest request = sra.getRequest();
String ip = request.getRemoteHost();
log.setUserIp(ip);
// 从session中获取用户信息
User loginUser = (User) request.getSession().getAttribute(ConstantParam.USER_TOKEN);
if (loginUser != null) {
log.setUserName(loginUser.getUserName());
} else {
if ("doLogin".equals(joinPoint.getSignature().getName())) {
log.setUserName(joinPoint.getArgs()[0].toString());
}
}
}
try {
Object object = joinPoint.proceed();
if (object != null) {
if (object instanceof Map) {
if(MapUtils.getBoolean((Map)object, "status")){
log.setOperResult("成功");
}else {
log.setOperResult(MapUtils.getString((Map) object, "msg"));
}
}
}
log.setWorkTime((System.currentTimeMillis() - beginTime) / 1000);
logService.insert(log);
return object;
} catch (Throwable e) {
log.setWorkTime((System.currentTimeMillis() - beginTime) / 1000);
log.setOperResult("失败:" + e.getMessage());
logService.insert(log);
return null;
}
}
}
定义完切面后,我们要在springmvc的配置文件中,指定代理
<!-- 加入Aspectj配置 -->
<aop:aspectj-autoproxy proxy-target-class="true"/>
注意:该配置必须写在springmvc的配置文件,由于我们现在的方法切入点是在controller层,如果你定义在spring的配置文件里面,不会起作用。这牵扯到父子容器的问题。spring默认的主配置文件为父容器,springmvc为子容器,子容器可以使用父容器里面的配置信息,但是父容器却无法使用子容器的配置信息。
由于,日志记录的过程中,我们要记录该操作属于那个模块,并且当前的操作类型是什么,我这里直接采用注解来定义。
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface OperAnnotation {
//模块名
String moduleName();
//操作内容
String option();
}
这样,当我们在需要添加日志的controller方法上面只用加上简单的注解功能,在aop切面拦截的时候,就可以通过注解拿到属于那个模块,那个操作。
例如我的登录controller:
@OperAnnotation(moduleName = "登录系统",option = "登录")
@ResponseBody
@RequestMapping(value = "doLogin", method = RequestMethod.POST)
public Map doLogin(@RequestParam("username") String username, @RequestParam("password") String password, HttpServletRequest request, HttpServletResponse response) {}
来源:oschina
链接:https://my.oschina.net/u/1471190/blog/679128