A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

#SpringAOP 记录操作日志
AOP称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等,Struts2的拦截器设计就是基于AOP的思想,是个比较经典的例子。
今天我们以主要讲解通过SpringAOP记录操作日志到数据库。
##实现思路

). 客户端发起请求 , 访问Controller中的insert方法
2). 在进入insert方法之前 , 被所定义的切面拦截 , 切入点表达式定义拦截Controller层中的方法
3). 由于需要记录controller中方法调用时请求参数名/值 , 以及返回值 , 操作人 等信息 , 在定义的通知中使用环绕通知类型进行日志记录;
4). 在定义的通知中获取请求参数信息 , 请求模块 , 请求的Controller类 , 方法等信息 ;
5). 放行原始方法的执行 , 并记录返回值 , 及其类型等信息 , 并将日志插入数据库

##所需依赖
创建一个工程, pom.xml 中添加如下依赖:

<dependencies>
    <!-- 引入Spring的依赖 -->
    <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-core</artifactId>
     <version>4.2.4.RELEASE</version>
    </dependency>
    <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-beans</artifactId>
     <version>4.2.4.RELEASE</version>
    </dependency>
    <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-context</artifactId>
     <version>4.2.4.RELEASE</version>
    </dependency>
    <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-expression</artifactId>
     <version>4.2.4.RELEASE</version>
    </dependency>
    <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-aop</artifactId>
     <version>4.2.4.RELEASE</version>
    </dependency>
    <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-aspects</artifactId>
     <version>4.2.4.RELEASE</version>
    </dependency>
   
    <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-tx</artifactId>
     <version>4.2.4.RELEASE</version>
    </dependency>
    <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-web</artifactId>
     <version>4.2.4.RELEASE</version>
    </dependency>
    <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-webmvc</artifactId>
     <version>4.2.4.RELEASE</version>
    </dependency>
  
  <!-- 引入Log4j的依赖 -->
    <dependency>
     <groupId>org.apache.logging.log4j</groupId>
     <artifactId>log4j-api</artifactId>
     <version>2.7</version>
    </dependency>

    <dependency>
     <groupId>log4j</groupId>
     <artifactId>log4j</artifactId>
     <version>1.2.17</version>
    </dependency>
   
    <!-- 引入servlet的依赖 -->
    <dependency>
     <groupId>javax.servlet</groupId>
     <artifactId>servlet-api</artifactId>
     <version>2.5</version>
     <scope>provided</scope>
    </dependency>

    <dependency>
     <groupId>org.aspectj</groupId>
     <artifactId>aspectjlib</artifactId>
     <version>1.6.2</version>
    </dependency>
   
    <!-- 时间处理的工具 -->
    <dependency>
     <groupId>joda-time</groupId>
     <artifactId>joda-time</artifactId>
     <version>2.5</version>
    </dependency>
   
    <!-- json转换工具fastjson -->
    <dependency>
     <groupId>com.alibaba</groupId>
     <artifactId>fastjson</artifactId>
     <version>1.2.31</version>
    </dependency>
   </dependencies>
  
  
   <build>
    <plugins>
     <!-- 配置tomcat插件 -->
     <plugin>
      <groupId>org.apache.tomcat.maven</groupId>
      <artifactId>tomcat7-maven-plugin</artifactId>
      <version>2.2</version>
      <configuration>
       <port>8080</port>
       <path>/</path>
      </configuration>
     </plugin>

   <!-- 配置编译插件 -->
     <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>3.2</version>
      <configuration>
       <target>1.7</target>
       <source>1.7</source>
       <encoding>UTF-8</encoding>
      </configuration>
     </plugin>
    </plugins>
   </build>
##定义日志信息对象
该对象主要封装的就是所要记录的日志信息
   

public class OperateLogMessage {

  //操作的模块名
  private String modelName;
  
  //操作的模块值
  private String modelValue;
  
  //返回值
  private String returnValue;
  
  //返回值类型
  private String returnClass;
  
  //操作人
  private String operateUser;
  
  //操作时间
  private String operateTime;
  
  //参数值 , 键值对形式
  private String paramAndValue;
  
  //操作的类
  private String operateClass;
  
  //操作的方法
  private String operateMethod;

  public String getModelName() {
   return modelName;
  }

  public void setModelName(String modelName) {
   this.modelName = modelName;
  }

  public String getModelValue() {
   return modelValue;
  }

  public void setModelValue(String modelValue) {
   this.modelValue = modelValue;
  }

  public String getReturnValue() {
   return returnValue;
  }

  public void setReturnValue(String returnValue) {
   this.returnValue = returnValue;
  }

  public String getReturnClass() {
   return returnClass;
  }

  public void setReturnClass(String returnClass) {
   this.returnClass = returnClass;
  }

  public String getOperateUser() {
   return operateUser;
  }

  public void setOperateUser(String operateUser) {
   this.operateUser = operateUser;
  }

  public String getOperateTime() {
   return operateTime;
  }

  public void setOperateTime(String operateTime) {
   this.operateTime = operateTime;
  }

  public String getParamAndValue() {
   return paramAndValue;
  }

  public void setParamAndValue(String paramAndValue) {
   this.paramAndValue = paramAndValue;
  }

  public String getOperateClass() {
   return operateClass;
  }

  public void setOperateClass(String operateClass) {
   this.operateClass = operateClass;
  }

  public String getOperateMethod() {
   return operateMethod;
  }

  public void setOperateMethod(String operateMethod) {
   this.operateMethod = operateMethod;
  }
}
##自定义注解
该注解主要用来起到标示以及记录方法形参的作用

    @Inherited
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OperateLog {
  
  //模块名
  String modelName();
  
  //模块的值
  String modelValue();
  
  //请求参数
  String[] params();
  
}
##定义业务处理类Controller
UserController是具体业务处理的类 , 将刚才声明的自定义注解作用在Controller的方法上, 用来自定义一些信息;
    @Controller
@RequestMapping("/user")
public class UserController {

  @Autowired
  private UserService userService;
  
  //在Controller的方法上声明 @OperateLog注解, 并通过注解的属性 , 记录操作的模块名, 模块值, 参数
  @RequestMapping(value="insert")
  @OperateLog(modelName="user" , modelValue="insert" , params={"name","age","gender"})
  public String insert(String name , Integer age , String gender){
   userService.insertUser(name , age , gender);
   return "success";
  }
  
  
  @RequestMapping(value="update")
  public String update(String name , Integer age , String gender){
   userService.insertUser(name , age , gender);
   return "success";
  }
}
##定义通知
定义通知类 operateAdvice , 该通知类中定义了两个环绕通知 , 环绕通知所配置的切入点配置为拦截所有的 cn.itcast.controller 下的所有的类的所有方法 ;
   
@Component
@Aspect
public class OperateAdvice {
  
  private static Logger log = Logger.getLogger(OperateAdvice.class);
  
  
  /**
   * 第一种方案 : 通过 execution 配置 @annotation 来实现注解的拦截
   *
   * @param pjp
   * @param operateLog
   * @return
   * @throws Throwable
   */
  @Around("execution(* cn.itcast.controller.*.*(..)) && @annotation(operateLog)")
  public Object insertLogAround(ProceedingJoinPoint pjp , OperateLog operateLog) throws Throwable{
   System.out.println(" *********************************** 方案一 [start]  ****************************** ");
   
   //定义日志记录对象
   OperateLogMessage op = new OperateLogMessage();
   
   //从注解中获取模块名 , 模块值  , 参数
   String modelName = operateLog.modelName();
   String modelValue = operateLog.modelValue();
   String[] params = operateLog.params();
   
   Map<String,String> paramMap = new HashMap<String, String>();
   
   //获取request对象, 并通过request对象获取参数信息
   HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
   
   if(params!=null){
    for (String param : params) {
     String paramValue = request.getParameter(param);
     paramMap.put(param, paramValue);
    }
   }
   
   //将获取的数据, 封装到模型对象中
   op.setModelName(modelName);
   op.setModelValue(modelValue);
   op.setOperateTime(new DateTime().toString("yyyy-MM-dd HH:mm:ss"));
   op.setOperateUser("10001");
   
   //记录操作的方法名及所在的类
   op.setOperateClass(pjp.getTarget().getClass().getName());
   op.setOperateMethod(pjp.getSignature().getName());
   
   //记录参数信息 , json格式的字符串
   op.setParamAndValue(JsonUtils.obj2JsonString(paramMap));
   
   //放行
   Object object = pjp.proceed();
   
   //记录返回值的类型 , 及返回值
   op.setReturnClass(object.getClass().getName());
   op.setReturnValue(object.toString());
   
   //输出日志到日志文件 , 当然也可以在此处操作数据库, 将数据记录在数据库
   log.info(JsonUtils.obj2JsonString(op));
   
   System.out.println(" *********************************** 方案一 [end]  ****************************** ");
   
   //返回结果
   return object;
  }
  
  
  
  
  
  
  
  
  
  /**
   * 第二种方案 : 直接通过 切入点表达式进行拦截, 然后在内部进行判定.
   *
   * @param pjp
   * @return
   * @throws Throwable
   */
  @Around("execution(* cn.itcast.controller.*.*(..))")
  public Object insertLogAround2(ProceedingJoinPoint pjp) throws Throwable{
   
   System.out.println(" *********************************** 方案二 [start] ****************************** ");
   
   //定义日志记录对象
   OperateLogMessage op = new OperateLogMessage();
   
   Map<String,String> paramMap = new HashMap<String, String>();
   
   //获取request对象, 并通过request对象获取参数信息
   HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
   
   //获取所拦截的方法上的注解 @OperateLog
   OperateLog opLog = getMethodOperateLog(pjp);
   
   //如果获取到的注解不为null , 则代表该方法需要记录日志信息
   if(opLog != null) {
    op.setModelName(opLog.modelName());
    op.setModelValue(opLog.modelValue());
   
    //获取请求参数 , 并封装到Map中
    String[] params = opLog.params();
    if(params!=null){
     for (String param : params) {
      String paramValue = request.getParameter(param);
      paramMap.put(param, paramValue);
     }
    }
   
    //记录操作时间
    op.setOperateTime(new DateTime().toString("yyyy-MM-dd HH:mm:ss"));
    op.setOperateUser("10001"); // 操作人这里只是写死的,实际是要从session中获取用户
   
    //记录操作的类, 已经操作的方法名
    op.setOperateClass(pjp.getTarget().getClass().getName());
    op.setOperateMethod(pjp.getSignature().getName());
   
    //记录请求的参数
    op.setParamAndValue(JsonUtils.obj2JsonString(paramMap));
   
    //放行
    Object object = pjp.proceed();
   
    //记录返回值的类型 , 返回值
    op.setReturnClass(object.getClass().getName());
    op.setReturnValue(object.toString());
   
    //输出日志到日志文件 , 当然也可以在此处操作数据库, 将数据记录在数据库
    log.info(JsonUtils.obj2JsonString(op));
   
    System.out.println(" *********************************** 方案二 [end] ****************************** ");
   
    return object;
   
   //如果没有获取到@OperateLog注解 , 代表不需要记录日志, 直接放行
   }else{
    Object object = pjp.proceed();
    return object;
   }
   
  }
  
  
  
  /**
      * 获取拦截的方法上的  @OperateLog 注解
      *
      */  
     private OperateLog getMethodOperateLog(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();  
           OperateLog operateLog = null;  
            
    //获取方法上的注解
            for(Method method : methods) {  
                if(method.getName().equals(methodName)) {  
                   Class[] clazzs = method.getParameterTypes();  
                    if(clazzs.length == arguments.length) {
                    operateLog = method.getAnnotation(OperateLog.class);  
                       break;  
                   }  
               }  
           }  
           return operateLog;  
       }  
      
     
}
方式一是通过切入点表达式中的@annotation(operateLog) 标识 , 获取到所拦截的@OperateLog注解 , 从而获取到注解中的属性值, 再通过request获取到请求的参数 , 方法执行的返回值, 以及返回值的类型 ;
方式二是通过反射获取到作用的有 @OperateLog注解的方法 , 若标识的有@OperateLog注解, 说明该方法的执行需要日志跟踪 。 从而获取到注解中的属性值, 再通过request获取到请求的参数 , 方法执行的返回值, 以及返回值的类型 ;
当然 , 这个入门程序中是直接将所有拿到的跟踪的日志信息 , 输出在了控制台 , 或日志文件中; 如果有需要, 也完全可以将日志信息记录在数据库表结构中 ;
##Spring的配置文件
    <beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:mvc="http://www.springframework.org/schema/mvc"
  xmlns:context="http://www.springframework.org/schema/context"
  xmlns:aop="http://www.springframework.org/schema/aop"
  xmlns:tx="http://www.springframework.org/schema/tx"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
   http://www.springframework.org/schema/mvc
   http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd
   http://www.springframework.org/schema/context
   http://www.springframework.org/s ... ing-context-4.2.xsd
   http://www.springframework.org/schema/aop
   http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
   http://www.springframework.org/schema/tx
   http://www.springframework.org/schema/tx/spring-tx-4.2.xsd ">
  
  <context:component-scan base-package="cn.itcast">
   <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
  </context:component-scan>
  
</beans>
##SpringMVC的配置文件
    <beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:mvc="http://www.springframework.org/schema/mvc"
  xmlns:context="http://www.springframework.org/schema/context"
  xmlns:aop="http://www.springframework.org/schema/aop"
  xmlns:tx="http://www.springframework.org/schema/tx"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
   http://www.springframework.org/schema/mvc
   http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd
   http://www.springframework.org/schema/context
   http://www.springframework.org/s ... ing-context-4.2.xsd
   http://www.springframework.org/schema/aop
   http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">
  
  <!-- 配置报扫描 -->
  <context:component-scan base-package="cn.itcast.controller"/>
  
  <!-- 开启aop的自动代理 -->
  <aop:aspectj-autoproxy proxy-target-class="true"/>
  
  <!-- mvc注解驱动 -->
  <mvc:annotation-driven/>
  
  <!-- 视图解析器 -->
  <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
   <property name="prefix" value="/WEB-INF/jsp/" />
   <property name="suffix" value=".jsp" />
  </bean>
</beans>

##web.xml的配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
    <display-name>spring-aop-log</display-name>
    <welcome-file-list>
      <welcome-file>index.html</welcome-file>
      <welcome-file>index.htm</welcome-file>
      <welcome-file>index.jsp</welcome-file>
      <welcome-file>default.html</welcome-file>
      <welcome-file>default.htm</welcome-file>
      <welcome-file>default.jsp</welcome-file>
    </welcome-file-list>
   
    <!-- 该监听器用来加载spring的配置文件, 初始化spring容器 -->
    <listener>
      <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <context-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:applicationContext.xml</param-value>
    </context-param>
   
    <!-- 该监听器用来将request对象放置在当前线程ThreadLocal中 , 便于在程序中直接获取request -->
    <listener>
      <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
    </listener>
   
    <!-- springmvc的前端控制器 -->
    <servlet>
      <servlet-name>springMVC</servlet-name>
      <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
      <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:springmvc.xml</param-value>
      </init-param>
    </servlet>
   
    <servlet-mapping>
      <servlet-name>springMVC</servlet-name>
      <url-pattern>*.action</url-pattern>
    </servlet-mapping>
   
</web-app>

##通过Tomcat插件启动
执行tomcat7:run指令, 并访问controller
##测试结果
     *********************************** 方案一 [start]  ******************************
  *********************************** 方案二 [start] ******************************
2018-03-31 21:00:04,292 [http-bio-8080-exec-1] ERROR [cn.itcast.aop.OperateAdvice] - {"modelName":"user","modelValue":"insert","operateClass":"cn.itcast.controller.UserController","operateMethod":"insert","operateTime":"2018-03-31 21:00:04","operateUser":"10001","paramAndValue":"{\"age\":\"20\",\"name\":\"zhangsan\",\"gender\":\"1\"}","returnClass":"java.lang.String","returnValue":"success"}
  *********************************** 方案二 [end] ******************************
2018-03-31 21:00:04,293 [http-bio-8080-exec-1] ERROR [cn.itcast.aop.OperateAdvice] - {"modelName":"user","modelValue":"insert","operateClass":"cn.itcast.controller.UserController","operateMethod":"insert","operateTime":"2018-03-31 21:00:04","operateUser":"10001","paramAndValue":"{\"age\":\"20\",\"name\":\"zhangsan\",\"gender\":\"1\"}","returnClass":"java.lang.String","returnValue":"success"}
  *********************************** 方案一 [end]  ******************************
输出的日志信息中, 可以详细的看到当前操作的模块, 操作人, 操作时间 , 传递参数名, 传递参数值, 返回值, 返回值类型 等等信息 ;
这些日志记录下来 , 在后期就可以进行有效的日志跟踪 , 系统监控 , 当然这只是AOP其中的一个常用的用法而已 , AOP还用一些其他的用途 , 等待着大家继续探究。

0 个回复

您需要登录后才可以回帖 登录 | 加入黑马