這幾天在學習Spring的AOP,然後自己做了一個功能,這個功能是這樣的:用戶進行對數據進行添加操作之後,系統將操作的用戶的ID、操作的模塊和操作的內容記錄到數據庫中。
AOP相關術語:
JoinPoint:連接點,指的是可以被攔截的點
PointCut:切入點,指的是真正被攔截的點
Advice:增強,指的是攔截切入點後需要做得事
Target:對象,指的是使用增強的那個對象類
Wearing:織入,指的是將增強應用到對象類的過程
Aspect:切面,指的是切入點與增強的組合
aop實現原理其實是java動態代理,但是jdk的動態代理必須實現接口,所以spring的aop是用cglib這個庫實現的,cglib使用了asm這個直接操縱字節碼的框架,所以可以做到不實現接口的情況下完成動態代理。(轉)
實驗開始
1.數據庫
logs表:(此表記錄着用戶操作的信息)
2.數據庫操作層
實體類:
package com.myhomes.entity;
import java.util.Date;
public class Logs {
private Integer id;
private Date operationTime;
private String types;
private String operator;
private String modules;
private String operation;
private String result;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getModules() {
return modules;
}
public void setModules(String modules) {
this.modules = modules;
}
public Date getOperationTime() {
return operationTime;
}
public void setOperationTime(Date operationTime) {
this.operationTime = operationTime;
}
public String getTypes() {
return types;
}
public void setTypes(String types) {
this.types = types;
}
public String getOperator() {
return operator;
}
public void setOperator(String operator) {
this.operator = operator;
}
public String getOperation() {
return operation;
}
public void setOperation(String operation) {
this.operation = operation;
}
public String getResult() {
return result;
}
public void setResult(String result) {
this.result = result;
}
}
接口類:
package com.myhomes.dao;
import com.myhomes.entity.Logs;
import org.springframework.stereotype.Repository;
@Repository("logsDao")
public interface LogsDao {
void insertLogs(Logs log);
}
映射實現:
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.4//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.myhomes.dao.LogsDao">
<resultMap id="logs" type="Logs">
<result property="id" column="id" javaType="Integer"></result>
<result property="operationTime" column="operation_time" javaType="java.sql.Date"></result>
<result property="types" column="types" javaType="String"></result>
<result property="operator" column="operator" javaType="String"></result>
<result property="modules" column="modules" javaType="String"></result>
<result property="operation" column="operation" javaType="String"></result>
<result property="result" column="result" javaType="String"></result>
</resultMap>
<insert id="insertLogs" parameterType="Logs" useGeneratedKeys="true" keyColumn="id">
insert into logs(operation_time,types,operator,modules,operation,result) value(#{operationTime},#{types},#{operator},#{modules},#{operation},#{result})
</insert>
</mapper>
3.增強類
新建一個增強類,我是在服務層那裏建的。
因爲操作日誌是一定要知道是誰操作的,而如何獲取操作者,我看有些朋友是通過HttpServletRequest獲得session,再從session獲得Attribute來知道用戶身份的。但是個人感覺session不好,而且我的增強是放在服務層的,服務層沒有web層的類,就比如這個類:HttpServletRequest,如果要用這個類,還要在服務層pom文件導入web層的pom文件,同時還將服務層的spring文件導入web層的spring文件的內容,麻煩得緊。而且這麼做的話在IDEA啓動tomcat還要確認導包之類什麼的。。。
這個例子我還是有用到session(等我把話說完哈),在前端把session中的用戶信息放在一個公用的地方,賦值到一個hidden屬性的a標籤中,提交添加請求的時候獲取a標籤的內容(也就是用戶在數據庫表中唯一的id)與數據一同提交到服務器上,只要我把代碼修改不要session是能做到的(就是用戶登錄之後在轉發頁面的時候帶上用戶的id,在每次用戶需要自己的id進行提交操作時都發送給服務器,服務器處理之後再給會這個id給用戶)。
package com.myhomes.biz.advice;
import com.myhomes.biz.LogsBiz;
import com.myhomes.entity.Logs;
import org.aspectj.lang.JoinPoint;
import org.springframework.beans.factory.annotation.Autowired;
public class LogsAdvice {
@Autowired
private LogsBiz logsBiz;
public void addOperationLog(JoinPoint joinPoint){
Logs logs = new Logs();
//target對象
logs.setModules(joinPoint.getTarget().getClass().getSimpleName());
//操作方法
logs.setOperation(joinPoint.getSignature().getName());
//操作者
//System.out.println("後置增強:joinPoint.getArgs()[0].toString():"+joinPoint.getArgs()[0].toString());
logs.setOperator(joinPoint.getArgs()[0].toString());
//增強方法訪問成功
logs.setResult("1");
logsBiz.addOperationLog(logs);
}
}
4.Spring文件中配置增強
最後那個配置
<aop:pointcut>標籤指的是切入點,id作爲切入點的唯一標識,execution定義使用要增強的目標方法;
<aop:aspect>標籤指的是切面,ref屬性選擇ico注入的增強類。
<aop:after-returning>標籤指的是“後置增強”,method屬性指的是增強方法,pointcut-ref屬性選擇相關的切入點id。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--聲明式事務配置在業務層-->
<!--導入dao層配置文件-->
<import resource="spring-dao.xml"/>
<!--開啓自動掃描-->
<context:component-scan base-package="com.myhomes.biz"/>
<!--aop自動代理-->
<aop:aspectj-autoproxy/>
<!--事務管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--聲明通知,定義規則-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="get*" read-only="true"/> <!--不用事務封裝-->
<tx:method name="find*" read-only="true"/>
<tx:method name="search*" read-only="true"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="LogsAdvice" class="com.myhomes.biz.advice.LogsAdvice"></bean>
<!--通知和切入點進行關聯-->
<aop:config>
<aop:pointcut id="addPointCut" expression="execution(* com.myhomes.controller.MonthCostController.add*(..))"/> <!--第一個*號代表任意返回值-->
<aop:aspect ref="LogsAdvice">
<!--<aop:before method="operationLog" pointcut-ref="pointcut1"></aop:before>-->
<aop:after-returning method="addOperationLog" pointcut-ref="addPointCut"></aop:after-returning>
</aop:aspect>
</aop:config>
</beans>
5.服務層
接口類:(最後那個)
package com.myhomes.biz;
import com.myhomes.entity.Logs;
public interface LogsBiz {
void addSystemLog(Logs logs);
void addLoginLog(Logs logs);
void addOperationLog(Logs logs);
}
實現類:(最後那個)
package com.myhomes.biz.impl;
import com.myhomes.biz.LogsBiz;
import com.myhomes.dao.LogsDao;
import com.myhomes.entity.Logs;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import java.util.Date;
@Service("logsBiz")
public class LogsBizImpl implements LogsBiz {
@Autowired
@Qualifier(value = "logsDao")
private LogsDao logsDao;
public void addSystemLog(Logs logs) {
logs.setOperationTime(new Date());
//System
logs.setTypes("Sys");
logsDao.insertLogs(logs);
}
public void addLoginLog(Logs logs) {
logs.setOperationTime(new Date());
logs.setTypes("Login");
logsDao.insertLogs(logs);
}
public void addOperationLog(Logs logs) {
logs.setOperationTime(new Date());
//Operation
logs.setTypes("Ope");
logsDao.insertLogs(logs);
}
}
6.控制器層
將要用到增強的add方法,類的其他方法和add()的方法體代碼省略了,以免影響觀看。
package com.myhomes.controller;
import com.myhomes.biz.HouseBiz;
import com.myhomes.biz.MonthCostBiz;
import com.myhomes.biz.UserService;
import com.myhomes.entity.House;
import com.myhomes.entity.MonthCost;
import com.myhomes.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Controller("monthCostController")
@RequestMapping(value = "/count")
public class MonthCostController {
@Autowired
private HouseBiz houseBiz;
@Autowired
private UserService userService;
@Autowired
private MonthCostBiz monthCostBiz;
@RequestMapping(value = "/add")
@ResponseBody
@Transactional
public Map<String,Object> addMonthCost(@RequestBody Map<String,String> map) throws ParseException {
略略略
}
}
7.頁面
利用session將用戶id放到a標籤裏
8.js/jquery
注意看$.ajax({});前面,這裏獲取用戶的id,並且跟數據一同提交到服務器。
//添加數據提交
$("#count_house_btn").click(function () {
//判斷必填選項是否爲空
if (!$("#count_house_HouseId").val()||!$("#counthouse1").val()||!$("#rent").val()||!$("#waterMeter").val()||!$("#powerMeter").val()){
alert("必選內容不能爲空!");
return false;
}
if ($("#rent").val()*1<200){
alert("租金最低爲200!");
return false;
}
//判斷水費度數是否比上月小
if ($("#waterMeter").val()*1 < lastWaterMeter*1){
alert("水費度數不能低於上月水費度數!");
return false;
}
//判斷電費度數是否比上月小
if ($("#powerMeter").val()*1 < lastPowerMeter*1){
alert("電費度數不能低於上月電費度數!");
return false;
}
//判斷本次年月日是否比上次計算的日期爲同一日
if ($("#counthouse1").val() === lastMonth){
alert("本日已計算此房間房租,無須再次計算!");
return false;
}
//判斷本次年月日是否比上次計算的日期要早
var lastmonth = lastMonth;
var lastmonth2 = lastmonth.replace(/-/g,'');
var thismonth = $("#counthouse1").val();
var thismonth2 = thismonth.replace(/-/g,'');
if (thismonth2*1 < lastmonth2*1){
alert("本次計算房租時間不能比上次計算的房租時間要早!");
return false;
}
//判斷本次年月日是否比現在晚
var date = new Date();
var year = date.getFullYear();
var month = date.getMonth()+1;
var day = date.getDate();
if (month < 10) {
month = "0" + month;
}
if (day < 10) {
day = "0" + day;
}
var nowDate = year + month + day;
if (nowDate*1 < thismonth2*1){
alert("日期不能比現在晚!");
return false;
}
//判斷網費是否小於0
if ($("#network").val()*1 < 0){
alert("網費不能爲負數!");
return false;
}
var houseId = $("#count_house_HouseId").val();
var yearsMonth = $("#counthouse1").val();
var rent = $("#rent").val()*1;
var waterMeter2 = $("#waterMeter").val()*1;
var powerMeter2 = $("#powerMeter").val()*1;
var network = $("#network").val()*1;
var clean = $("#clean").val()*1;
var sums = $("#sum_input").val()*1;
//獲取上月水錶數與電錶數
var lastWaterMeter = $("#lastWaterMeterLabel").text();
var lastPowerMeter = $("#lastPowerMeterLabel").text();
//用水電錶差值進行水電費計算
var waterCost = (waterMeter2 - lastWaterMeter) *2.5;
var powerCost = (powerMeter2 - lastPowerMeter) *1.5;
//提交用戶id
var uid = $("#uid").text();
// alert("waterCost:"+waterCost+",powerCost:"+powerCost);
$.ajax({
type:"post",
url:"/count/add",
dataType : "json",
contentType : "application/json;charset=UTF-8",
data:JSON.stringify({
uid:uid,
houseId:houseId,
yearsMonth:yearsMonth,
rent:rent,
waterMeter:waterMeter2,
powerMeter:powerMeter2,
waterCost:waterCost,
powerCost:powerCost,
network:network,
clean:clean,
sum:sums,
lastMonth:lastMonth
}),
success:function (data) {
if (data.msg === "add") {
$("#counthouse1").val("");
$("#rent").val("");
$("#waterMeter").val("");
$("#powerMeter").val("");
$("#network").val("");
alert("添加成功!");
window.location.href='/count/view';
}else if (data.msg === "out") {
$("#counthouse1").val("");
$("#rent").val("");
$("#waterMeter").val("");
$("#powerMeter").val("");
$("#network").val("");
if(data.deposit1 >=0){
alert("退房成功!需要向住戶返還押金!"+data.deposit1+"元!");
}else if(data.deposit2 < 0){
alert("退房成功!押金不夠抵押本月房租,需要向住戶拿!"+data.deposit2+"元!");
}
window.location.href='/count/view';
}else{
alert("系統出錯!");
}
}
});
});
});
9.演示
第一步:
結果:
交互成功,系統進行了添加的操作!
第二步:查看數據庫日誌表內容
系統記錄了uid=1,(operator字段中,並且有添加的數據),操作時間有,日誌類型爲操作型,是某個控制器模塊,result是“1”表示成功。
第三步:查看用戶表id字段爲“1”的用戶是誰
10.後言
爲什麼我不僅把uid記錄到數據庫中,而且還把添加的信息都記錄進去了?因爲我個人覺得單單是記錄某個人對數據庫表進行添加操作的信息是不夠的,需要把添加的信息都加進去會好很多。如果是用於修改的增強方法,還要把修改的那一條數據的id給加入到日誌信息進去。
11.補
如果日後要搞個日誌查看的模塊,這樣設計operator字段內容不是很好,因爲是要直接顯示操作者是誰,如果只顯示1大串內容交互不是很好,我修改了下代碼,把操作人字段內容就僅添加操作者的id,result字段內容就直接是添加的數據內容和uid。
public class LogsAdvice {
@Autowired
private LogsBiz logsBiz;
public void addOperationLog(JoinPoint joinPoint){
Logs logs = new Logs();
//target對象
logs.setModules(joinPoint.getTarget().getClass().getSimpleName());
//操作方法
logs.setOperation(joinPoint.getSignature().getName());
//操作者
//System.out.println("後置增強:joinPoint.getArgs()[0].toString():"+joinPoint.getArgs()[0].toString());
Map<String,String> map = (Map<String, String>) joinPoint.getArgs()[0];
logs.setOperator(map.get("uid"));
//增強方法訪問成功
logs.setResult(joinPoint.getArgs()[0].toString());
logsBiz.addOperationLog(logs);
}
}