咱們知道,在一個系統中,業務操做日誌對咱們很重要。那麼以往咱們更多的時候是寫個方法,而後哪一個模塊須要加入日誌,就引入這個接口。可是這種其實冗餘了不少代碼。今天我簡單給你們介紹下采用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) {}