本文核心:AspectJ【Aop】实现对管理员操作的监视,并将日志信息记录在数据库中。难点,在Service层获取HttpSession。
这系列博客是记录自己做过项目学到知识。one、two代表项目编号。
前言:
后台管理系统
技术选型:ssm + easyui + ajax + js/jq + jsp + mysql + redis + webservice
工具版本:jdk1.8 + tomcat7/8 + mysql8.0.11 + idea2017 + spring4.1.13
项目规范:maven3.3.9 + git
一:AspectJ
1. AOP框架AspectJ,简化AOP代码(通过注解简化AOP)
2. AspactJ框架常用注解
@Aspect //声明切面 【注解类上】
@Order(1) //该注解用于指定多切面执行顺序(@Order 值越小优先级越高)
@PonitCut // 声明切入点的注解
@Before // 声明前置通知注解
@After // 声明后置通知注解(原始方法执行正常或者非正常执行都会进入)
@AfterReturing // 声明后置通知注解(原始方法必须正常执行)
@AfterThrowing // 声明异常通知
@Around // 环绕通知注解
二:AspectJ实战 【记录增、删、改操作的日志】
日志需要记录的信息
user : session.getAttribute("user");//管理名 存在于HttpSession中【难点所在】
操作时间:sysdate() now() //系统时间
操作资源类型:xxxServiceImpl //xxx
动作: methodName 【add/update/delete】
操作详细信息:method.getArgs();//方法的形参
操作结果:success //操作结果
Aop实现日志操作本身没有什么难点。但是日志要求的字段:日志记录信息有User,用户登录成功,Session存有用户信息。但是Session存在哪里?一般情况存在于Controller层中,Service层获取不到Session。
那么实现日志操作的难点:① Advice中的around(ProceedingJoinPoint pjp)获取日志需要的字段
②如何在Service层访问到HttpSession
解决:获取上述日志的需要的字段【对Service实现类和增删改方法的命名有一定的要求】
xxxServiceImpl 【业务层实现方法】
queryXXXXX 查询
addXXXXX 添加
modifyXXXXXX 更改
/*设置代理功能
* pjp.getArgs()->获取方法的参数信息
*pjp.getSignature()->获取代理执行的方法
*pjp.getTarget().toString()->获取代理类的全限定名 ->截取资源名
* */
解决:Service层获取HttpSession。 18年-8月-1日【修改、完善】
【
第一种:ThreadLocal绑定Session。 【需要考虑绑定和移除时机,建议通过Filter拦截请求和响应完成】
第二种:RequestContextHolder 对象获取HttpServletRequest对象,间接获取HttpSession。
】
①:配置web.xml
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
②:通过 RequestContextHolder 获取Request对象,再通过Request对象获取Session对象
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
全部该功能涉及的源码及配置:
Spring 的配置
<!--使用aspectJ 风格的Aop-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<!--配置增强方法-->
<bean id="myAdvice" class="com.baizhi.advice.MyAdvice"/>
日志实体类
public class Log implements Serializable {
private Integer id;
private String user;
private java.util.Date time;
private String resource;
private String action;
private String message;
private String result;
public Log() {
}
//省略get/set方法
}
MyAdvice源码:
/**
* @Description 切面方法
* @Author Maps
* @Time 2018/7/9 19:46
* @Version 1.0
*/
@Aspect
public class MyAdvice {
@Autowired
private LogService logService;
/*定义切点*/
@Pointcut("execution(* com.baizhi.service.impl.*.add*(..))||execution(* com.baizhi.service.impl.*.modify*(..))")
public void cut(){}
/*设置代理功能
* pjp.getArgs()->获取方法的参数信息
*pjp.getSignature()->获取代理执行的方法
*pjp.getTarget().toString()->获取代理类的全限定名 ->截取资源名
* */
@Around("cut()")
public Object around(ProceedingJoinPoint pjp) {
//声明日志对象
Log log=new Log();
//设置流程成功,失败回滚【默认】
log.setResult("success");
//设置消息 -->参数 操作结果、成功或失败
Object[] args = pjp.getArgs();
log.setMessage(args.toString());
//设置操作名
MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
Method method = methodSignature.getMethod();
String name= method.getName();//获取方法名
if(name.startsWith("modify")){
log.setAction("modify");
}else{
log.setAction("add");
}
//获取资源名
String resource= pjp.getTarget().toString();
int start=resource.lastIndexOf(".");
int end=resource.lastIndexOf("ServiceImpl");
log.setResource(resource.substring(start+1,end));
//获取session中的用户名
String managerName=(String) RequestContextHolder.currentRequestAttributes().getAttribute("managerName", RequestAttributes.SCOPE_SESSION);
log.setUser(managerName);
Object object=null;
try {
//调用原函数
object=pjp.proceed();
} catch (Throwable throwable) {
log.setResult("fail");
throwable.printStackTrace();
}
//写入日志
logService.writeLog(log);
return object;
}
}
最后衷心提示Log日志添加信息操作的方法名,不要以add开头。因为我切入点选入的时Service层,注入了LogService来完成日志操作,会造成栈溢出。【因为logService的方法以add开头,符合切点表达式,会再次进入切面,无限递归】
@Pointcut("execution(* com.baizhi.service.impl.*.add*(..))||execution(* com.baizhi.service.impl.*.modify*(..))")
可以看出,我的切点表达式以方法名前缀做了区分。