Springboot 集成aop 实现日志的记录

一、Spring AOP(概念)

Aspect Oriented Programming:面向切面编程,是OOP(面向对象编程的补充和完善)。

(1)什么时候会出现面向切面编程的需求?

按照软件重构的思想,如果多个类中出现重复的代码,就该考虑定义一个共同的抽象类,将这些共有的代码提取到抽象类中,我们称为纵向抽取。但当我们想要给所有类方法添加性能检测、事务控制或者存取用户操作日志时,Aop将这些分散在各个业务逻辑代码中的相同代码,通过横向切割的方式抽取到一个独立模块中。

(2)解决的主要问题:

将这些独立的逻辑融合到业务逻辑中,完成跟原来一样的业务逻辑。

(3)Aop术语

  • 切面(Aspect)

切面是一个横切关注点的模块化,一个切面能够包含同一个类型的不同增强方法,比如说事务处理和日志处理可以理解为两个切面。切面由切入点通知组成,它既包含了横切逻辑的定义,也包括了切入点的定义。 Spring AOP就是将切面所定义的横切逻辑织入到切面所指定的连接点中。

可以简单理解为,含有@Aspect注解的类就说切面

  • 目标对象(Target)

指的是要被增强的对象,即包含主业务逻辑的类对象。或者说是被一个或多个切面所通知的对象。

  • 连接点(JoinPoint)

连接点就是被拦截到的程序执行点,因为Spring只支持方法类型的连接点,所以在Spring中连接点就是被拦截到的方法。

  • 切入点(PointCut)

切入点是对连接点进行拦截的条件定义。切入点表达式如何和连接点匹配是AOP的核心,Spring缺省使用AspectJ切入点语法。 
 一般认为,所有的方法都可以认为是连接点,但是我们并不希望在所有的方法上都添加通知,而切入点的作用就是提供一组规则(使用 AspectJ pointcut expression language 来描述) 来匹配连接点,给满足规则的连接点添加通知。

  • 通知(Advice)

通知是指拦截到连接点之后要执行的代码。通知分为以下几种:

  1. 前置通知(before):在执行业务代码前做的操作,比如获取连接对象
  2. 后置通知(after):在执行业务代码后做的操作,比如关闭连接对象
  3. 异常通知(afterThrowing):执行业务代码后出现异常,需要做的操作,比如回滚事务
  4. 返回通知(afterReturning):在执行业务代码后无异常,会执行的操作
  5. 环绕通知(around)
  • 织入(Weaving)
  • 增强器(Adviser)

二、日志记录的实现(实践)

基于Springboot框架的项目需求如下:

Springboot 集成aop 实现日志的记录_第1张图片

1.添加依赖

        
			org.springframework.boot
			spring-boot-starter-aop
		

2.创建一个自定义的注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysLog {

	String value() default "";
}

3.添加日志切面

/**
 * 切点类
 */
@Aspect    
@Component    
public  class SystemLogAspect {    
    //注入Service用于把日志保存数据库    
    @Resource    
    private LogService logServiceImp; 
   
    //本地异常日志记录对象    
    private  static  final Logger logger = LoggerFactory.getLogger(SystemLogAspect. class);
    
    //Controller层切点    
    @Pointcut("@annotation(com.taiji.sms.foundation.annotation.SysLog)")    
     public  void controllerAspect() {    
    }    
    
    /**  
     * 前置通知 用于拦截Controller层记录用户的操作 
     * @param joinPoint 切点  
     */    
     @Before("controllerAspect()")    
     public void doBefore(JoinPoint joinPoint) {    
    
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();       
        //读取session中的用户    
        UserInfoEntity user=(UserInfoEntity)HttpContextUtils.getValueFromHttpSession("UserInfoEntity");
        //请求的IP    
        //String ip = request.getRemoteAddr();
        
        String requestURI=request.getRequestURI();
        
        String ip=WebUtils.getRemoteAddr(request);
        String method = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
        String params = ""; 
        if (joinPoint.getArgs() !=  null && joinPoint.getArgs().length > 0) {    
            for ( int i = 0; i < joinPoint.getArgs().length; i++) {    
           	 params+=JsonUtils.objectToJson(joinPoint.getArgs()[i])+";";   
           }    
       }    
         try {    
            //*========控制台输出=========*//    
            String operation=getControllerMethodDescription(joinPoint);    
            String username=user.getUsername();
            TbLogEntity log=new TbLogEntity();
            log.setCreateTime(MyUtil.getNowDateStr2());
            log.setIp(ip);
            log.setOperation(operation);
            log.setParams(params);
            log.setUsername(username);
            log.setMethod(requestURI);
			//*========保存数据库日志=========*// 
            logServiceImp.insLog(log);
            //保存数据库    
        }  catch (Exception e) {    
            //记录本地异常日志    
            logger.error("前置通知异常,异常信息:{}", e.getMessage(), e);    
        }    
    }    
    
    /**  
     * 获取注解中对方法的描述信息 用于Controller层注解  
     *  
     * @param joinPoint 切点  
     * @return 方法描述  
     * @throws Exception  
     */    
     public  static String getControllerMethodDescription(JoinPoint joinPoint)  throws Exception {    
        String targetName = joinPoint.getTarget().getClass().getName();    
        String methodName = joinPoint.getSignature().getName();    
        Object[] arguments = joinPoint.getArgs();    
        Class targetClass = Class.forName(targetName);    
        Method[] methods = targetClass.getMethods();    
        String description = "";    
         for (Method method : methods) {    
             if (method.getName().equals(methodName)) {    
                Class[] clazzs = method.getParameterTypes();    
                 if (clazzs.length == arguments.length) {    
                    description = method.getAnnotation(SysLog.class).value();    
                     break;    
                }    
            }    
        }    
         return description;    
    }
}    

4.方法调用

/**
 * 退出
 */
	@RequestMapping("/logout")
	@SysLog(value = "用户退出登录")
	public ModelAndView logout(HttpServletRequest request){
		ModelAndView mView=new ModelAndView();
		mView.setViewName("logout");
		return mView;
	}

关于记录用户操作日志,对注解自定义的理解也是很重要的,默默打开书去复习注解啦,bye~~~~❤

你可能感兴趣的:(spring,boot,aop)