咱们知道,在一个系统中,业务操做日志对咱们很重要。那么以往咱们更多的时候是写个方法,而后哪一个模块须要加入日志,就引入这个接口。可是这种其实冗余了不少代码。今天我简单给你们介绍下采用spring AOP来统一帮咱们处理业务操做日志。java
首先看下个人日志表的设计:web
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;
接下来,建立咱们的日志实体类:spring
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:sql
@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不懂的同窗请自行查阅资料学习。session
我这里采用的通知类型为环绕通知。下面直接上代码:mvc
@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的配置文件中,指定代理app
<!-- 加入Aspectj配置 --> <aop:aspectj-autoproxy proxy-target-class="true"/>
注意:该配置必须写在springmvc的配置文件,因为咱们如今的方法切入点是在controller层,若是你定义在spring的配置文件里面,不会起做用。这牵扯到父子容器的问题。spring默认的主配置文件为父容器,springmvc为子容器,子容器可使用父容器里面的配置信息,可是父容器却没法使用子容器的配置信息。ide
因为,日志记录的过程当中,咱们要记录该操做属于那个模块,而且当前的操做类型是什么,我这里直接采用注解来定义。学习
@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) {}