[企业权限管理系统](九)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用以存注解的值),可见阅读源码是十分有必要的
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值