这几天在学习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);
}
}