AOP
数据库表结构
- 日志表的信息描述如下所示,用来下记录下任何操作
- 日志的实现主要i用到了SpringAOP技术前置通知和后置通知,前置通知获得当前访问时间,访问的类和方法,参数;通过后置通知计算访问时间,url然后调用syslogservice将数据全部插入数据库
PLSQL建表
CREATE TABLE sysLog(
id VARCHAR2(32) default SYS_GUID() PRIMARY KEY,
visitTime timestamp,
username VARCHAR2(50),
ip VARCHAR2(30),
url VARCHAR2(50),
executionTime int,
method VARCHAR2(200)
)
实体类
public class SysLog {
private String id;
private Date visitTime;
private String visitTimeStr;
private String username;
private String ip;
private String url;
private Long executionTime;
private String method;
}
LogAop
- heima_ssm_web/src/main/java/com.itheima.ssm.controller下新建一个LogAop的类
- 该切面类用以处理日志
一些日志信息的获取
执行时长
再后置通知中获取当前时间,减去前置通知中拿到的时间visittime()就可以获得访问时长
获取访问的url
(该url为控制器上的和方法上请求的组合)需要通过反射机制
-
在后置通知中先判断拿到的类和方法不为空且不为当前的LogAop类
-
获取该类上的@RequestMapping,拿到注解类对象,该对象额值为数组类型
//1.获取类上的@RequestMapping("/orders") RequestMapping classAnnotation = (RequestMapping) clazz.getAnnotation(RequestMapping.class); if (classAnnotation != null) { String[] classValue = classAnnotation.value(); //2.获取方法上的@RequestMapping(xxx) //在前置通知中已经拿到了访问的方法的实例对象 //通过该实例对象的getAnnotation(注解名.class)获取注解的实例对象 RequestMapping methodAnnotation = method.getAnnotation(RequestMapping.class); if (methodAnnotation != null) { //获取到后进行拼接 String[] methodValue = methodAnnotation.value(); url = classValue[0] + methodValue[0];
-
这些代码里大量的利用了反射机制对注解进行了操作
获取访问者ip
-
获取request对象即可获得ip
-
在web.xml中配置Spring提供的一个监听器Listener,RequestContextLisener
//注入一个RequestContextLisener @Autowired private HttpServletRequest request; //获取访问的ip String ip = request.getRemoteAddr();
操作者(name)的获取
可以通过securityContext(Spring提供的对象)获取,也可以从request.getSession中获取
//从上下文中获了当前登录的用户
SecurityContext context = SecurityContextHolder.getContext();
//得到的是 SecurityContext 中的User,需要强转
User user = (User) context.getAuthentication().getPrincipal();
String username = user.getUsername();
日志信息的封装
将日志相关信息封装到SysLog对象,封装过程都在后置通知中进行
日志记录操作
SysLogController
heima_ssm_web/src/main/java/com.itheima.ssm.controller下新建一个SysLogController用来处理前端的日志查询请求
Service
用一个ISysLogService完成日志记录操作
@Service
@Transactional
public class SysLogServiceImpl implements ISysLogService {
@Autowired
private ISysLogDao sysLogDao;
@Override
public List<SysLog> findAll() throws Exception {
return sysLogDao.findAll();
}
@Override
public void save(SysLog sysLog) throws Exception {
sysLogDao.save(sysLog);
}
}
Dao
package com.itheima.ssm.dao;
import com.itheima.ssm.domain.SysLog;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import java.util.List;
public interface ISysLogDao {
@Insert("insert into syslog(visitTime,username,ip,url,executionTime,method) values(#{visitTime},#{username},#{ip},#{url},#{executionTime},#{method})")
public void save(SysLog sysLog) throws Exception;
@Select("select * from sysLog")
List<SysLog> findAll() throws Exception;
}
代码
LogAOP
@Component
@Aspect
public class LogAop {
@Autowired
private HttpServletRequest request;
@Autowired
private ISysLogService sysLogService;
private Date visitTime; //开始时间
private Class clazz; //访问的类
private Method method;//访问的方法
//前置通知 主要是获取开始时间,执行的类是哪一个,执行的是哪一个方法
//拦截了所有controller下所有类所有方法
@Before("execution(* com.itheima.ssm.controller.*.*(..))")
public void doBefore(JoinPoint jp) throws NoSuchMethodException {
//当前时间就是开始访问的时间
visitTime = new Date();
//具体要访问的类
//jp为连接点,jp.getTarget.getClass()可以获得当前连接点的类对象
clazz = jp.getTarget().getClass();
//获取访问的方法的名称
//jp.getSignature().getName()
String methodName = jp.getSignature().getName();
//获取访问的方法的参数
Object[] args = jp.getArgs();
//获取具体执行的方法的Method对象
if (args == null || args.length == 0) {
//只能获取无参数的方法
method = clazz.getMethod(methodName);
} else {
//拿到每一个参数放到Args数组中
Class[] classArgs = new Class[args.length];
for (int i = 0; i < args.length; i++) {
classArgs[i] = args[i].getClass();
}
clazz.getMethod(methodName, classArgs);
}
}
//后置通知
//同样拦截所有类和所有方法
@After("execution(* com.itheima.ssm.controller.*.*(..))")
public void doAfter(JoinPoint jp) throws Exception {
long time = new Date().getTime() - visitTime.getTime(); //获取访问的时长
String url = "";
//获取url
if (clazz != null && method != null && clazz != LogAop.class) {、
//1.获取类上的@RequestMapping("/orders")
RequestMapping classAnnotation = (RequestMapping) clazz.getAnnotation(RequestMapping.class);
if (classAnnotation != null) {
String[] classValue = classAnnotation.value();
//2.获取方法上的@RequestMapping(xxx)
RequestMapping methodAnnotation = method.getAnnotation(RequestMapping.class);
if (methodAnnotation != null) {
String[] methodValue = methodAnnotation.value();
url = classValue[0] + methodValue[0];
//获取访问的ip
String ip = request.getRemoteAddr();
//获取当前操作的用户
SecurityContext context = SecurityContextHolder.getContext();//从上下文中获了当前登录的用户
User user = (User) context.getAuthentication().getPrincipal();
String username = user.getUsername();
//将日志相关信息封装到SysLog对象
SysLog sysLog = new SysLog();
sysLog.setExecutionTime(time); //执行时长
sysLog.setIp(ip);
sysLog.setMethod("[类名] " + clazz.getName() + "[方法名] " + method.getName());
sysLog.setUrl(url);
sysLog.setUsername(username);
sysLog.setVisitTime(visitTime);
//调用Service完成操作
sysLogService.save(sysLog);
}
}
}
}
}
SysLogController
package com.itheima.ssm.controller;
import com.itheima.ssm.domain.SysLog;
import com.itheima.ssm.service.ISysLogService;
import org.apache.log4j.net.SyslogAppender;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import java.util.List;
@Controller
@RequestMapping("/sysLog")
public class SysLogController {
@Autowired
private ISysLogService sysLogService;
@RequestMapping("/findAll.do")
public ModelAndView findAll() throws Exception {
ModelAndView mv=new ModelAndView();
List<SysLog> sysLogList= sysLogService.findAll();
mv.addObject("sysLogs",sysLogList);
mv.setViewName("syslog-list");
return mv;
}
}
-
切面类的配置要在web工程的/Controller包下中配置,同时还一定要配置前端日志查询请求的SysLogController切面类中的方法是不需要主动调用的
@Before("execution(* com.itheima.ssm.controller.*.*(..))")
该行注解说明在com.itheima.ssm.controller..(…))该包下任何请求发生前都会执行前置通知
@After("execution(* com.itheima.ssm.controller.*.*(..))")
该行注解说明在com.itheima.ssm.controller..(…))该包下任何请求的事件发生后都会执行该后置通知
断点调试
日志查询
- 在页面上提供了一个访问日志的接口
- 跳转到syslog-list.jsp页面展示
项目难点
前置通知
-
通过前置通知去拦截所有控制器的所有方法,在前置通知中获取当前时间直接new date,通过连接点joinypoint对象获取当前访问的类
-
在获取当前方法时,需要通过连接点获取名字后再获取参数数组object[] args = jp.getArgs()
- 为空则直接用得到的类meathod = clazz.getMethod(methodName) 拿到方法
- 不为空时候,将args赋值给一个参数数组再结此前拿到的方法名 调用clazz.getMethod(methodName, classArgs)拿到方法
后置通知
参数的获取
- 像执行的方法获取,需要利用连接点的静态方法,无参方法和有参方法也有错区别
- url的获取,是通过控制器上和方法上的@RequestMapping注解的值组合起来,这里要先拿拿到之前拿到的当前的类对象,通过类对象获取注解对象,再通过注解对象获取其value数组,由于之前只知道注解的使用,在此处阅读了注解的源码加深了对注解的理解(比如它底层用了一个String[] values用以存注解的值),可见阅读源码是十分有必要的