Spring MVC 中使用AOP 进行统一日志管理--注解实现

1.AOP简介

AOP称为面向切面编程

 AOP的基本概念

(1)Aspect(切面):通常是一个类,里面可以定义切入点和通知

(2)JointPoint(连接点):程序执行过程中明确的点,一般是方法的调用

(3)Advice(通知):AOP在特定的切入点上执行的增强处理,有before,after,afterReturning,afterThrowing,around

(4)Pointcut(切入点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式

(5)AOP代理:AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类

2.为什么要这么做?

AOP最常见的用法是进行日志管理和事务管理。现在我们重点说日志,下一节我们再介绍日志。

加入没使用AOP日志,但是为了日常维护方便和查找错误方便,我们要输入日志的话只能使用log.info()输出,关键是每一个需要日志的地方都要进行重复操作,而且不同的人输出的日志格式或者日志信息不统一,不便于使用。所以,我们使用切面,将日志服务作为一个组件动态地切入到我们需要地方,统一管理。

3.实现

我是在以前搭建的Spring MVC 工程基础上实现的日志功能。

首先在原来的基础上添加

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
     <version>1.9.1</version>
</dependency>

这个jar包的版本要和jdk匹配,否则会报错,jdk1.7以上必须使用1.7以上版本,为了避免报错,我直接使用了最高版本的。另外还要保证maven jar 保证有    这两个jar包

4.项目配置

项目配置中特别需要注意的是我是给controller层添加日志服务,而controller层是由Spring MVC 进行管理的,所以我们的日志服务组件也要确保能被spring MVC 扫到,而且要在spring MVC 配置文件中天AOP命名空间的支持,同时开启aop 支持。我之前走了很多弯路,就算因为我把这些配置都写在了spring的配置文件中,导致切面类一直无法生效。

<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans"  
  xmlns:p="http://www.springframework.org/schema/p"  
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  xmlns:context="http://www.springframework.org/schema/context"  
  xmlns:mvc="http://www.springframework.org/schema/mvc" 
  xmlns:aop="http://www.springframework.org/schema/aop" 
  xsi:schemaLocation="  
    http://www.springframework.org/schema/beans  
    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd  
    http://www.springframework.org/schema/context  
    http://www.springframework.org/schema/context/spring-context-3.2.xsd  
    http://www.springframework.org/schema/mvc  
    http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
    http://www.springframework.org/schema/aop    
    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"
    >  
       
     <mvc:annotation-driven />   
     <!-- 扫描controller(controller层注入) -->  
     <context:component-scan base-package="com.lzl.sss.controller"/>
      
     <context:component-scan base-package="com.lzl.sss.aop"/> 
     
     <aop:aspectj-autoproxy proxy-target-class="true"/>  
       
     <!-- 视图解析器 -->
    <bean id="viewResolver"
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/view/"> </property>
        <property name="suffix" value=".jsp"></property>
    </bean> 
    
</beans>  

5.切面类

package com.lzl.sss.aop;

import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Enumeration;

import javax.servlet.http.HttpServletRequest;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

@Component
@Aspect
//定义切面类
public class WebLogAspect {
    private Logger logger = LoggerFactory.getLogger(WebLogAspect.class);
    
    //定义切入点
    @Pointcut("execution(* com.lzl.sss.controller.*.*(..))")
    public void log(){
        
    }
    
    //定义通知,方法执行前
    @Before("log()")
    public void doBefore(JoinPoint poin) throws UnsupportedEncodingException{
        logger.info("方法执行前,当前时间:"+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 记录下请求内容
        logger.info("请求URL : " + request.getRequestURL().toString());
        logger.info("请求方法 : " + request.getMethod());
        logger.info("IP地址 : " + request.getRemoteAddr());
        Enumeration<String> enu = request.getParameterNames();
        while (enu.hasMoreElements()) {
            String name = (String) enu.nextElement();
            logger.info("参数:{},值:{}", name,new String(request.getParameter(name).getBytes("ISO-8859-1"),"utf-8"));
        }


    }
    
    //定义通知,方法执行后
    @After("log()")
    public void after(JoinPoint poin){
        logger.info("方法执行后,当前时间:"+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
    }
    
    //定义通知,方法返回前
    @AfterReturning(pointcut="log()",returning="returnVal")
    public void AfterReturning(JoinPoint poin){
        logger.info("方法返回前,当前时间:"+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
    }
    
    //定义通知,抛出异常
    @AfterThrowing(pointcut="log()",throwing="error")
    public void AfterThrowing(Throwable error){
        logger.info("方法报错,当前时间:"+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
    }
    
    //定义通知环绕型
    @Around("log()")
    public Object Around (ProceedingJoinPoint pjp) throws Throwable{
        logger.info("环绕前:"+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        Object obj= pjp.proceed();
        logger.info("环绕后:"+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        return obj;
    }
}

切面类有以下几个注意的点:

(1).切点表达式。

第一个*表示匹配任意的方法返回值,..(两个点)表示零个或多个,上面的第一个..表示controller包及其子包,第二个*表示所有类,第三个*表示所有方法,第二个..表示方法的任意参数个数

(2).环绕型通知

环绕型通知在方法执行前后都会执行。且要添加返回类型,否则会导致连接点执行的方法无返回值。

(3).通知类型的执行顺序

正常情况:around--->before--->要执行的方法--->around---->after--->afterReturnning

报错情况下:around--->before--->要执行的方法--->around---->after--->afterThrowing

(4)注解

@Component 注解将类声明为一个组件,并注入到spring MVC 容器中

@Aspect 将这个bean修饰成一个切面类

6.实现效果

原文地址:https://www.cnblogs.com/li-zhi-long/p/9391885.html