xml地图|网站地图|网站标签 [设为首页] [加入收藏]

自定义注解必威,Aspect方式记录日志

项目描述:项目采用SpringMVC搭建,采用注解方式事物管理使用全注解方式,如下:

项目需求


**需求1: **web项目一般而言都需要日志记录,比较常用的是实用log4j来记录项目的异常日志,将日志单独存储于文件当中,这样有利于我们快速进行bug 排解。
**需求2: **异常的记录一般就是将异常的堆栈保存在文件中,这样文件大小会急剧上升,有效异常信息也不能被立即定位,有没有一种方式可以可以让我们重写异常记录,并保存在异常日志文件当中呢。
**需求3: **在异常日志之上,我们一般还需要对系统中各角色的各个重要操作进行一些日志记录,以方便我们找到操作人,以及责任人。

针对1中的需求,我们大家熟悉的是实用log4j进行日志记录。配置简单,实现快速。
针对2,3中的需求我们一般采用的是基于拦截器实现(Aop思想的一种实现方式)在方法操作之前进行一定的处理,获取操作人、操作方法名、操作参数,异常捕获与记录,这样实现也是完全可以的。
今天记录的是基于自定义注解面向切面(AOP)进行统一操作日志以及异常日志记录的实现。

   系统日志对于定位/排查问题的重要性不言而喻,相信许多开发和运维都深有体会。

本节主要内容:

beanpropertyname="dataSource"ref="dataSource"//beantx:annotation-driventransaction-manager="transactionManager"/

项目代码


项目中代码如下所示:
1.首先定义两个注解:分别为SystemControllerLog(用于拦截Controller层操作注解,起切点表达式作用,明确切面应该从哪里注入),SystemServiceLog(用于拦截Service层操作注解,起切点表达式作用,明确切面应该从哪里注入)
这两个注解在切面中定义切点表达式的时候会用到。
SystemControllerLog.java

package com.fxmms.common.log.logannotation;
import java.lang.annotation.*;

/**
 * Created by mark on 16/11/25.
 * @usage 自定义注解,拦截Controller
 */
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SystemControllerLog {
    String description() default "";
}

SystemServiceLog

package com.fxmms.common.log.logannotation;
import java.lang.annotation.*;

/**
 * Created by mark on 16/11/25.
 * @usage 自定义注解 拦截service
 */
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SystemServiceLog {
    String description() default "";
}

2.接下来定义切面类,这里面主要定义了几个通知,在调用被代理对象目标方法前后,或目标方法抛出异常之后使用。

package com.fxmms.common.log.logaspect;
import com.fxmms.common.log.logannotation.SystemControllerLog;
import com.fxmms.common.log.logannotation.SystemServiceLog;
import com.fxmms.common.security.ScottSecurityUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
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;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;

/**
 * 切点类
 * @author tiangai
 * @since 2014-08-05 Pm 20:35
 * @version 1.0
 */
@Aspect
@Component
public class SystemLogAspect {
    //注入Service用于把日志保存数据库 nodo service 层实现

    //本地异常日志记录对象
    private static final Logger logger = LoggerFactory.getLogger(SystemLogAspect.class);

    /**
     * Service层切点 使用到了我们定义的 SystemServiceLog 作为切点表达式。
     * 而且我们可以看出此表达式基于 annotation。
     *
     */
    @Pointcut("@annotation(com.fxmms.common.log.logannotation.SystemServiceLog)")
    public void serviceAspect() {
    }

    /**
     * Controller层切点 使用到了我们定义的 SystemControllerLog 作为切点表达式。
     * 而且我们可以看出此表达式是基于 annotation 的。
     */
    @Pointcut("@annotation(com.fxmms.common.log.logannotation.SystemControllerLog)")
    public void controllerAspect() {
    }

    /**
     * 前置通知 用于拦截Controller层记录用户的操作
     *
     * @param joinPoint 连接点
     */
    @Before("controllerAspect()")
    public void doBefore(JoinPoint joinPoint) {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        //请求的IP
        String ip = request.getRemoteAddr();
        System.out.println(ip+"sdsdsdsdsd");
        try {
            //控制台输出
            System.out.println("=====前置通知开始=====");
            Object object = joinPoint.getTarget();
            System.out.println("请求方法:" + (joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()"));
            System.out.println("方法描述:" + getControllerMethodDescription(joinPoint));
            System.out.println("请求人:" + ScottSecurityUtil.getLoginName());
            System.out.println("请求IP:" + ip);
            //构造数据库日志对象

            //保存数据库

            System.out.println("=====前置通知结束=====");
        } catch (Exception e) {
            //记录本地异常日志
            logger.error("==前置通知异常==");
            logger.error("异常信息:{}", e.getMessage());
        }
    }

    /**
     * 异常通知 用于拦截service层记录异常日志
     *
     * @param joinPoint
     * @param e
     */
    @AfterThrowing(pointcut = "serviceAspect()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        //获取请求ip
        String ip = request.getRemoteAddr();
        //获取用户请求方法的参数并组织成字符串
        String params = "";
        if (joinPoint.getArgs() != null && joinPoint.getArgs().length > 0) {
            for (int i = 0; i < joinPoint.getArgs().length; i++) {
                params += joinPoint.getArgs()[i]+ ",";
            }
        }
        try {
            //控制台输出
            System.out.println("=====异常通知开始=====");
            System.out.println("异常代码:" + e.getClass().getName());
            System.out.println("异常信息:" + e.getMessage());
            System.out.println("异常方法:" + (joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()"));
            System.out.println("方法描述:" + getServiceMthodDescription(joinPoint));
            System.out.println("请求人:" + ScottSecurityUtil.getLoginName());
            System.out.println("请求IP:" + ip);
            System.out.println("请求参数:" + params);
            //构造数据库日志对象

            //保存数据库

            System.out.println("=====异常通知结束=====");
        } catch (Exception ex) {
            //记录本地异常日志
            logger.error("==异常通知异常==");
            logger.error("异常信息:{}", ex);
        }
         //录本地异常日志
        logger.error("异常方法:{}异常代码:{}异常信息:{}参数:{}", joinPoint.getTarget().getClass().getName() + joinPoint.getSignature().getName(), e.getClass().getName(), e.getMessage(), params);
    }

    /**
     * 获取注解中对方法的描述信息 用于service层注解
     *
     * @param joinPoint 切点
     * @return 方法描述
     * @throws Exception
     */
    public static String getServiceMthodDescription(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(SystemServiceLog.class).description();
                    break;
                }
            }
        }
        return description;
    }

    /**
     * 获取注解中对方法的描述信息 用于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(SystemControllerLog.class).description();
                    break;
                }
            }
        }
        return description;
    }
}

上面的切面类中定义了公共切点 serviceAspectserviceAspect,并实现了Controller层的前置通知,Service业务逻辑层的异常通知。
其中预留了保存日志到数据库的代码段,我们可以根据业务自行进行填充。
3.创建好切点、切面类之后,如何让他起作用呢,我们需要在配置文件中进行配置了。我将web项目中关于不同层的配置文件进行的切割,数据访问层配置文件是data-access.xml、业务逻辑层是service-application.xml、控制层是defalut-servlet.xml
首先看defalut-servlet.xml中的配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/mvc
       http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <!--开启aop-->
    <aop:aspectj-autoproxy proxy-target-class="true"/>
    <mvc:annotation-driven>
        <!--json解析-->
        <mvc:message-converters>
            <bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
            <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
        </mvc:message-converters>
    </mvc:annotation-driven>

    <context:component-scan base-package="com.fxmms.www.controller">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    <!--扫描日志记录切面-->
    <context:component-scan base-package="com.fxmms.common.log" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/>
    </context:component-scan>
    <!--配置异常处理器-->
    <context:component-scan base-package="com.fxmms.common.exception_handler" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/>
    </context:component-scan>
    <!--因为web.xml中defaultDispatcherServlet对所有请求进行了拦截,所以对一些.css .jpg .html .jsp也进行了拦截,所以此配置项
    保证对对静态资源不拦截-->
    <mvc:default-servlet-handler/>
    <!--视图解析器-->
    <bean id="viewResolver"
          class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
    <!--配置文件上上传-->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="defaultEncoding" value="utf-8"/>
        <property name="maxUploadSize" value="10485760000"/>
        <property name="maxInMemorySize" value="40960"/>
    </bean>
</beans>

注意:以上配置有两个重要的点:

1.项,

  1. <aop:aspectj-autoproxy proxy-target-class="true"/>
    proxy-target-class="true"默认是false,更改为true时使用的是cglib动态代理。这样只能实现对Controller层的日志记录。

service-application.xml配置AOP,实现对Service层的日志记录.

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:task="http://www.springframework.org/schema/task"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
  http://www.springframework.org/schema/aop
  http://www.springframework.org/schema/aop/spring-aop.xsd
  http://www.springframework.org/schema/tx
  http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/task
        http://www.springframework.org/schema/task/spring-task.xsd">
    <!--开启AOP-->
    <aop:aspectj-autoproxy/>

    <!--设置定时任务-->
    <task:annotation-driven/>
    <context:component-scan base-package="com.fxmms.www" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
    </context:component-scan>
    <!--ioc管理切面-->
    <context:component-scan base-package="com.fxmms.common.log" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/>
    </context:component-scan>

    <!-- enable the configuration of transactional behavior based on annotations -->
    <tx:annotation-driven transaction-manager="txManager"/>

    <bean id="txManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>

</beans>

这样Service也是可以实现操作、异常日志记录了。
4.在代码中使用自定义注解,相当于在目标方法上设置了一个切点,通过切点注入切面。
Controller层上运用SystemControllerLog注解:
TestNullPointExceptionController.java(验证Controller层中异常,Controller中调用Service层代码)

package com.fxmms.www.controller.admin;

import com.fxmms.common.log.logannotation.SystemControllerLog;
import com.fxmms.www.service.TestExceptionLog;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * Created by mark on 16/11/25.
 */
@Controller
public class TestNullPointExceptionController {
    private static Log logger = LogFactory.getLog(TestNullPointExceptionController.class);
    //自动注入一个Service层类对象
    @Autowired
    TestExceptionLog testExceptionLog;

    @ResponseBody
    @RequestMapping("/admin/testexcption")
    @SystemControllerLog(description = "testException")//使用   SystemControllerLog注解,此为切点
    public String testException(String str){
        return testExceptionLog.equalStr(str);
    }
}

** TestExceptionLog.java**

package com.fxmms.www.service;

import com.fxmms.common.log.logannotation.SystemServiceLog;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Service;

/**
 * Created by mark on 16/11/25.
 */
@Service
public class TestExceptionLog {
    private static Log logger = LogFactory.getLog(TestExceptionLog.class);

    @SystemServiceLog(description = "equalstr")
    public String equalStr(String str) {
        str = null;
        if (str.equals("sd")) {
            return "sd";
        }else {
            return "sd";
        }
    }
}

我在其中手动设置str = null,用于模拟前台输入。
程序在运行时会报运行时异常。
最终启动项目项目console日志输出如下图所示:

日志控制台输出

error.log输出

这样就完成了自定义注解&Aop&自定义异常&操作日志的记录,而且自定义的注解与切面可以进行重用,操作日志与异常日志可以进行数据库记录,后期甚至可以做一个关于异常分析的系统,我们可以直接从日志后台系统中查看异常出现的频率,以及定位异常的发声位置,明确操作人等。
完。

博客搬家:大坤的个人博客
欢迎评论哦~

   通过日志追踪代码运行状况,模拟系统执行情况,并迅速定位代码/部署环境问题。

 

现在想通过AOP自定义注解来记录系统日志,使用了@Before前置通知和@AfterThrowing异常通知,现在前置通知能进,而且能记录日志入库,但是异常通知遇到的情况是能进入,但是不能进行数据库操作,事物回滚了,这样我就没法记录错误异常日志,那么具体应该怎么配置才能实现抛出异常后记录异常日志数据库操作后再回滚事物呢?下面是AOP日志记录Spring当中配置:

   系统日志同样也是数据统计/建模的重要依据,通过分析系统日志能窥探出许多隐晦的内容。

一、Spring 通过XML配置文件形式来AOP 来实现前置,环绕,异常通知

aop:aspectj-autoproxyproxy-target-aop:includename="logAspect"//aop:aspectj-autoproxybean/

   如系统的健壮性(服务并发访问/数据库交互/整体响应时间...)

    1. Spring AOP  前置通知 XML配置使用案例

希望有了解AOP方面的高手来回答下

   某位用户的喜好(分析用户操作习惯,推送对口内容...)

    2. Spring AOP  **环绕通知 XML配置使用**案例**

   当然系统开发者还不满足于日志组件打印出来的日志,毕竟冗余且篇幅巨长。

    3. Spring AOP  必威,抛出异常后通知 XML配置使用案例

   so,对于关键的系统操作设计日志表,并在代码中进行操作的记录,配合 SQL 统计和搜索数据是件很愉快的事情。

**    4. Spring AOP  返回后通知 XML配置使用案例**

   本篇旨在总结在 Spring 下使用 AOP 注解方式进行日志记录的过程,如果能对你有所启发阁下不甚感激。

**    5. Spring AOP  后通知  XML配置使用案例**

1. 依赖类库

       <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>${aspectjweaver.version}</version>
        </dependency>

  AspectJ 中的很多语法结构基本上已成为 AOP 领域的标准。

  Spring 也有自己的 Spring-AOP,采用运行时生成代理类,底层可以选用 JDK 或者 CGLIB 动态代理。

  通俗点,AspectJ 在编译时增强要切入的类,而 Spring-AOP 是在运行时通过代理类增强切入的类,效率和性能可想而知。

  Spring 在 2.0 的时候就已经开始支持 AspectJ ,现在到 4.X 的时代已经很完美的和 AspectJ 拥抱到了一起。

  开启扫描 AspectJ 注解的支持:

    <!-- proxy-target-class等于true是强制使用cglib代理,proxy-target-class默认false,如果你的类实现了接口 就走JDK代理,如果没有,走cglib代理  -->
    <!-- 注:对于单利模式建议使用cglib代理,虽然JDK动态代理比cglib代理速度快,但性能不如cglib -->
    <aop:aspectj-autoproxy proxy-target-class="true"/>

 

2. 定义切入点日志注解

   必威 1

   目标操作日志表,其中设计了一些必要的字段,具体字段请拿捏具体项目场景,根据表结构设计注解如下。

@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OperationLog {

    String operationModular() default "";

    String operationContent() default "";
}

   上述我只做了两个必要的参数,一个为操作的模块,一个为具体的操作内容。

   其实根据项目场景这里参数的设计可以非常丰富,不被其他程序员吐槽在此一举。

二、Spring 通过注解形式来AOP 来实现前置,环绕,异常通知

3. 编写处理日志切点类

   @Pointcut("@annotation(com.rambo.spm.common.aop.OperationLog)")
    public void operationLogAspect() {

    }

   类的构造函数上描述了该类要拦截的为 OperationLog 的注解方法, 同样你也可以配置 XML 进行拦截。

   切入点的姿势有很多,不仅是正则同样也支持组合表达式,强大的表达式能让你精准的切入到任何你想要的地方。

   更多详情:

   看到这里如果你对 Spring AOP 数据库事务控制熟悉,其实 Spring AOP 记录日志是相似的机制。

    @Before("operationLogAspect()")
    public void doBefore(JoinPoint joinPoint) {
        logger.info("before aop:{}", joinPoint);
        //do something
    }

    @Around("operationLogAspect()")
    public Object doAround(ProceedingJoinPoint point) {
        logger.info("Around:{}", point);
        Object proceed = null;
        try {
            proceed = point.proceed();

            //do somthing
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            logger.error("日志 aop 异常信息:{}", throwable.getMessage());
        }
        return proceed;
    }

    @AfterThrowing("operationLogAspect()")
    public void doAfterThrowing(JoinPoint pjp) {
        logger.info("@After:{}", pjp);
        //do somthing
    }

    @After("operationLogAspect()")
    public void doAfter(JoinPoint pjp) {
        logger.info("@After:{}", pjp);
    }

    @AfterReturning("operationLogAspect()")
    public void doAfterReturning(JoinPoint point) {
        logger.info("@AfterReturning:{}", point);
    }

    AspectJ 提供了几种通知方法,通过在方法上注解这几种通知,解析对应的方法入参,你就能洞悉切点的一切运行情况。

   前置通知(@Before):在某连接点(join point)之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常);

   返回后通知(@AfterReturning):在某连接点(join point)正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回;

   抛出异常后通知(@AfterThrowing):方法抛出异常退出时执行的通知;

   后通知(@After):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出);

   环绕通知(@Around):包围一个连接点(joinpoint)的通知,如方法调用;

   通知方法中的值与构造函数一致,指定该通知对哪个切点有效,

   上述 @Around  为最强大的一种通知类型,可以在方法调用前后完成自定义的行为,它可选择是否继续执行切点、直接返回、抛出异常来结束执行。

   @Around 之所以如此强大是和它的入参有关,别的注解注解入参只容许 JoinPoint ,而 @Around 注解容许入参 ProceedingJoinPoint。

package org.aspectj.lang;

import org.aspectj.runtime.internal.AroundClosure;

public interface ProceedingJoinPoint extends JoinPoint {
    void set$AroundClosure(AroundClosure var1);

    Object proceed() throws Throwable;

    Object proceed(Object[] var1) throws Throwable;
}

   反编译 ProceedingJoinPoint 你会恍然大悟,Proceedingjoinpoint 继承了 JoinPoint 。

   在 JoinPoint 的基础上暴露出 proceed 这个方法。proceed 方法很重要,这是 aop 代理链执行的方法。

   暴露出这个方法,就能支持 aop:around 这种切面(而其他的几种切面只需要用到 JoinPoint,这跟切面类型有关), 能决定是否走代理链还是走自己拦截的其他逻辑。

   如果项目没有特定的需求,妥善使用 @Around 注解就能帮你解决一切问题。

    @Around("operationLogAspect()")
    public Object doAround(ProceedingJoinPoint point) {
        logger.info("Around:{}", point);
        Object proceed = null;
        try {
            proceed = point.proceed();

            Object pointTarget = point.getTarget();
            Signature pointSignature = point.getSignature();

            String targetName = pointTarget.getClass().getName();
            String methodName = pointSignature.getName();
            Method method = pointTarget.getClass().getMethod(pointSignature.getName(), ((MethodSignature) pointSignature).getParameterTypes());
            OperationLog methodAnnotation = method.getAnnotation(OperationLog.class);
            String operationModular = methodAnnotation.operationModular();
            String operationContent = methodAnnotation.operationContent();

            OperationLogPO log = new OperationLogPO();
            log.setOperUserid(SecureUtil.simpleUUID());
            log.setOperUserip(HttpUtil.getClientIP(getHttpReq()));
            log.setOperModular(operationModular);
            log.setOperContent(operationContent);
            log.setOperClass(targetName);
            log.setOperMethod(methodName);
            log.setOperTime(new Date());
            log.setOperResult("Y");
            operationLogService.insert(log);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            logger.error("日志 aop 异常信息:{}", throwable.getMessage());
        }
        return proceed;
    }

   别忘记将上面切点处理类/和要切入的类托管给 Spring,Aop 日志是不是很简单,复杂的应该是 aspectj 内部实现机制,有机会要看看源码哦。

   处理切点类完整代码:

必威 2必威 3

@Aspect
@Component
public class OperationLogAspect {
    private static final Logger logger = LoggerFactory.getLogger(OperationLogAspect.class);

    //ProceedingJoinPoint 与 JoinPoint
    //注入Service用于把日志保存数据库
    //这里我用resource注解,一般用的是@Autowired,他们的区别如有时间我会在后面的博客中来写
    @Resource
    private OperationLogService operationLogService;

    //@Pointcut("execution (* com.rambo.spm.*.controller..*.*(..))")
    @Pointcut("@annotation(com.rambo.spm.common.aop.OperationLog)")
    public void operationLogAspect() {

    }


    @Before("operationLogAspect()")
    public void doBefore(JoinPoint joinPoint) {
        logger.info("before aop:{}", joinPoint);
        gePointMsg(joinPoint);
    }

    @Around("operationLogAspect()")
    public Object doAround(ProceedingJoinPoint point) {
        logger.info("Around:{}", point);
        Object proceed = null;
        try {
            proceed = point.proceed();

            Object pointTarget = point.getTarget();
            Signature pointSignature = point.getSignature();

            String targetName = pointTarget.getClass().getName();
            String methodName = pointSignature.getName();
            Method method = pointTarget.getClass().getMethod(pointSignature.getName(), ((MethodSignature) pointSignature).getParameterTypes());
            OperationLog methodAnnotation = method.getAnnotation(OperationLog.class);
            String operationModular = methodAnnotation.operationModular();
            String operationContent = methodAnnotation.operationContent();

            OperationLogPO log = new OperationLogPO();
            log.setOperUserid(SecureUtil.simpleUUID());
            log.setOperUserip(HttpUtil.getClientIP(getHttpReq()));
            log.setOperModular(operationModular);
            log.setOperContent(operationContent);
            log.setOperClass(targetName);
            log.setOperMethod(methodName);
            log.setOperTime(new Date());
            log.setOperResult("Y");
            operationLogService.insert(log);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            logger.error("日志 aop 异常信息:{}", throwable.getMessage());
        }
        return proceed;
    }

    @AfterThrowing("operationLogAspect()")
    public void doAfterThrowing(JoinPoint pjp) {
        logger.info("@AfterThrowing:{}", pjp);

    }

    @After("operationLogAspect()")
    public void doAfter(JoinPoint pjp) {
        logger.info("@After:{}", pjp);
    }

    @AfterReturning("operationLogAspect()")
    public void doAfterReturning(JoinPoint point) {
        logger.info("@AfterReturning:{}", point);
    }

    private void gePointMsg(JoinPoint joinPoint) {
        logger.info("切点所在位置:{}", joinPoint.toString());
        logger.info("切点所在位置的简短信息:{}", joinPoint.toShortString());
        logger.info("切点所在位置的全部信息:{}", joinPoint.toLongString());
        logger.info("切点AOP代理对象:{}", joinPoint.getThis());
        logger.info("切点目标对象:{}", joinPoint.getTarget());
        logger.info("切点被通知方法参数列表:{}", joinPoint.getArgs());
        logger.info("切点签名:{}", joinPoint.getSignature());
        logger.info("切点方法所在类文件中位置:{}", joinPoint.getSourceLocation());
        logger.info("切点类型:{}", joinPoint.getKind());
        logger.info("切点静态部分:{}", joinPoint.getStaticPart());
    }

    private HttpServletRequest getHttpReq() {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
        return servletRequestAttributes.getRequest();
    }
}

View Code

   上述三步骤之后,你就可以在想记录日志的方法上面添加注解来进行记录操作日志,像下面这样。

必威 4

    源码托管地址:  有这方面需求和兴趣的可以检出到本地跑一跑。

  

    1. Spring AOP  前置通知  注解使用案例

**    2. Spring AOP  环绕通知  注解使用案例**

**    3. Spring AOP  抛出异常后通知  注解使用案例**

    4. Spring AOP  返回后通知  注解使用案例

    5. Spring AOP  后通知  注解使用案例

 

 

本文作者:souvc

本文出处: 

 

AOP是Aspect Oriented Programming的缩写,意思是面向方面编程,AOP实际是GoF设计模式的延续

 

关于Spring AOP的一些术语

  • 切面(Aspect):在Spring AOP中,切面可以使用通用类或者在普通类中以@Aspect 注解(@AspectJ风格)来实现
  • 连接点(Joinpoint):在Spring AOP中一个连接点代表一个方法的执行
  • 通知(Advice):在切面的某个特定的连接点(Joinpoint)上执行的动作。通知有各种类型,其中包括"around"、"before”和"after"等通知。许多AOP框架,包括Spring,都是以拦截器做通知模型, 并维护一个以连接点为中心的拦截器链
  • 切入点(Pointcut):定义出一个或一组方法,当执行这些方法时可产生通知,Spring缺省使用AspectJ切入点语法。

 

通知类型

 

  • 前置通知(@Before):在某连接点(join point)之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)
  • 返回后通知(@AfterReturning):在某连接点(join point)正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回
  • 抛出异常后通知(@AfterThrowing):方法抛出异常退出时执行的通知
  • 后通知(@After):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)
  • 环绕通知(@Around):包围一个连接点(join point)的通知,如方法调用。这是最强大的一种通知类型,环绕通知可以在方法调用前后完成自定义的行为,它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行

 

Spring 实现AOP是依赖JDK动态代理和CGLIB代理实现的。以下是JDK动态代理和CGLIB代理简单介绍    

    JDK动态代理:其代理对象必须是某个接口的实现,它是通过在运行期间创建一个接口的实现类来完成对目标对象的代理。
    CGLIB代理:实现原理类似于JDK动态代理,只是它在运行期间生成的代理对象是针对目标类扩展的子类。CGLIB是高效的代码生成包,底层是依靠ASM(开源的java字节码编辑类库)操作字节码实现的,性能比JDK强。   

    在Spring中,有接口时将采用JDK的方式实现proxy代理对象,当没有接口时,将采用cglib中的方式实现prixy代理对象。

 

 

一、 Spring 通过XML配置文件形式来AOP 来实现前置,环绕,异常通知

 

1 Spring AOP前置通知案例

1.1 问题

使用Spring AOP前置通知,在访问Controller中每个方法前,记录用户的操作日志。

1.2 方案

Spring AOP使用步骤:

1.3 步骤

实现此案例需要按照如下步骤进行。

 

步骤一:创建Controller,创建新项目SpringAOP。

导入Spring 环境的jar包 :

如果没有jar包,那么可以上去上面下一个。下载地址:  访问密码 6c96

 

创建员工业务控制器EmpController,并实现员工查询,代码如下:

 

 

package com.souvc.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
 * 控制类  
 *
 */
@Controller
@RequestMapping("/emp")
public class EmpController {

    /**
    * 方法名:find</br>
    * 详述: 查询员工  </br>
    * 开发人员:http://www.cnblogs.com/liuhongfeng/ </br>
    * 创建时间:2016年4月29日  </br>
    * @param userid
    * @param password
    * @return
    * @throws
     */
    @RequestMapping("/findEmp.do")
    public String find(String userid,String password) {

        // 模拟查询员工数据
        System.out.println("执行,find()方法,查询员工数据,发送至列表页面.");

        return "emp/emp_list.jsp";
    }
}

 

 

 

步骤二:创建方面组件

 

创建方面组件OperateLogger,并在该类中创建记录用户操作日志的方法,代码如下:

 

package com.souvc.aspect;

/**
 * 用于记录日志的方面组件,演示Spring AOP的各种通知类型。
 */
public class OperateLogger {

    /**
    * 方法名:log1</br>
    * 详述:测试前置通知 </br>
    * 开发人员:http://www.cnblogs.com/liuhongfeng/ </br>
    * 创建时间:2016年4月29日  </br>
    * @throws
     */
    public void log1() {
        // 记录日志
        System.out.println("进入log1()方法");
    }
}

 

 

步骤三:声明方面组件

在applicationContext.xml中,声明该方面组件,关键代码如下:

     

     <!-- 声明方面组件 -->  

    <bean id="operateLogger" class="com.souvc.aspect.OperateLogger"/>

 

 

步骤四:将方面组件作用到目标组件上

在applicationContext.xml中,将声明的方面组件作用到com.souvc.controller包下所有类的所有方法上,关键代码如下:

 

    <!-- 声明方面组件 -->
    <bean id="operateLogger" class="com.souvc.aspect.OperateLogger"/>

    <!-- 配置AOP -->
    <aop:config>
        <aop:aspect ref="operateLogger">
            <aop:before method="log1" pointcut="within(com.souvc.controller..*)"/>
        </aop:aspect>
    </aop:config> 

 

步骤五:测试

创建Junit测试类TestEmpController,并增加测试查询员工的方法,代码如下:

 

package com.souvc.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
 * 控制类  
 *
 */
@Controller
@RequestMapping("/emp")
public class EmpController {

    /**
    * 方法名:find</br>
    * 详述: 查询员工  </br>
    * 开发人员:http://www.cnblogs.com/liuhongfeng/ </br>
    * 创建时间:2016年4月29日  </br>
    * @param userid
    * @param password
    * @return
    * @throws
     */
    @RequestMapping("/findEmp.do")
    public String find(String userid,String password) {

        // 模拟查询员工数据
        System.out.println("执行,find()方法,查询员工数据,发送至列表页面.");

        return "emp/emp_list.jsp";
    }
}

 

 

执行该测试方法,控制台输出效果:

 

进入log1()方法
执行,find()方法,查询员工数据,发送至列表页面.

可见,在执行EmpController.find()方法之前,执行了方面组件的记录日志的方法,由于该方法采用AOP面向对象的思想实现的,因此不需要对Controller类做任何改动。

  

2 Spring AOP环绕通知案例

2.1 问题

使用Spring AOP环绕通知,在访问Controller中每个方法前,记录用户的操作日志。

2.2 方案

Spring AOP使用步骤:

2.3 步骤

实现此案例需要按照如下步骤进行。

 

步骤一:创建方面组件

复用方面组件OperateLogger,在该类中创建新的记录日志的方法log2,代码如下:

 

/**
    * 方法名:log2</br>
    * 详述:环绕通知使用的方法 </br>
    * 开发人员:http://www.cnblogs.com/liuhongfeng/ </br>
    * 创建时间:2016年4月29日  </br>
    * @param p
    * @return
    * @throws Throwable
    * @throws
     */
    public Object log2(ProceedingJoinPoint p) throws Throwable {
        // 目标组件的类名
        String className = p.getTarget().getClass().getName();
        // 调用的方法名
        String method = p.getSignature().getName();
        // 当前系统时间
        String date = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss") .format(new Date());
        // 拼日志信息
        String msg = "-->用户在" + date + ",执行了" + className + "." + method + "()";
        // 记录日志
        System.out.println(msg);
        // 执行目标组件的方法
        Object obj = p.proceed();
        // 在调用目标组件业务方法后也可以做一些业务处理
        System.out.println("-->调用目标组件业务方法后...");
        return obj;
    }

 

 

步骤二:声明方面组件

由于复用的方面组件已经声明,因此该步骤可以省略。

 

步骤三:将方面组件作用到目标组件上

在applicationContext.xml中,声明方面组件的log2方法,关键代码如下:

 

<aop:aspect ref="operateLogger">
   <aop:around method="log2" pointcut="within(com.souvc.controller..*)"/>
 </aop:aspect>

 

 步骤四:测试

执行测试方法TestEmpController.test1(),控制台输出效果如下:

 

进入log1()方法
-->用户在2016-04-29 01:06:54,执行了com.souvc.controller.EmpController.find()
执行,find()方法,查询员工数据,发送至列表页面.
-->调用目标组件业务方法后..

 

项目详细代码:

 

applicationContext.xml

 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:jpa="http://www.springframework.org/schema/data/jpa"
    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/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.2.xsd
            http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.2.xsd
            http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
            http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd
            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
            http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd">

    <!-- 开启注解扫描 -->
    <context:component-scan base-package="com.souvc" />

    <!-- 支持@RequestMapping请求和Controller映射 -->
    <mvc:annotation-driven />
    <bean
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/" />
        <property name="suffix" value=".jsp" />
    </bean>

    <!-- 声明方面组件--> 
    <bean id="operateLogger" class="com.souvc.aspect.OperateLogger" />

      <!-- 配置AOP -->
    <aop:config>

         <!-- 测试前置通知 -->
        <aop:aspect ref="operateLogger">
            <aop:before method="log1"  pointcut="within(com.souvc.controller..*)"/>
        </aop:aspect>

        <!-- 测试环绕通知 -->
        <aop:aspect ref="operateLogger">
            <aop:around method="log2" pointcut="within(com.souvc.controller..*)"/>
        </aop:aspect>


    </aop:config> 




</beans>

 

 

OperateLogger.java

 

package com.souvc.aspect;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.aspectj.lang.ProceedingJoinPoint;

/**
 * 用于记录日志的方面组件,演示Spring AOP的各种通知类型。
 */
public class OperateLogger {

    /**
    * 方法名:log1</br>
    * 详述:测试前置通知 </br>
    * 开发人员:http://www.cnblogs.com/liuhongfeng/ </br>
    * 创建时间:2016年4月29日  </br>
    * @throws
     */
    public void log1() {
        // 记录日志
        System.out.println("进入log1()方法");
    }



    /**
    * 方法名:log2</br>
    * 详述:环绕通知使用的方法 </br>
    * 开发人员:http://www.cnblogs.com/liuhongfeng/ </br>
    * 创建时间:2016年4月29日  </br>
    * @param p
    * @return
    * @throws Throwable
    * @throws
     */
    public Object log2(ProceedingJoinPoint p) throws Throwable {
        // 目标组件的类名
        String className = p.getTarget().getClass().getName();
        // 调用的方法名
        String method = p.getSignature().getName();
        // 当前系统时间
        String date = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss") .format(new Date());
        // 拼日志信息
        String msg = "-->用户在" + date + ",执行了" + className + "." + method + "()";
        // 记录日志
        System.out.println(msg);
        // 执行目标组件的方法
        Object obj = p.proceed();
        // 在调用目标组件业务方法后也可以做一些业务处理
        System.out.println("-->调用目标组件业务方法后...");
        return obj;
    }

}

 

 

 

3 Spring AOP异常通知案例

3.1 问题

使用Spring AOP异常通知,在每个Controller的方法发生异常时,记录异常日志。

3.2 方案

Spring AOP使用步骤:

3.3 步骤

实现此案例需要按照如下步骤进行。

步骤一:创建方面组件

复用方面组件OperateLogger,在该类中创建新的记录日志的方法log3,代码如下:

 

 /**
   * 方法名:log3</br>
   * 详述:测试异常通知使用的方法</br>
   * 开发人员:http://www.cnblogs.com/liuhongfeng/  </br>
   * 创建时间:2016年4月29日  </br>
   * @param e
   * @throws
    */
    public void log3(Exception e) {
        StackTraceElement[] elems = e.getStackTrace();
        // 将异常信息记录
        System.out.println("-->" + elems[0].toString());
    }

 

 

步骤二:声明方面组件

由于复用的方面组件已经声明,因此该步骤可以省略。

 

步骤三:将方面组件作用到目标组件上

在applicationContext.xml中,声明方面组件的log3方法,关键代码如下:

 

 <aop:aspect ref="operateLogger">
            <aop:after-throwing method="log3" throwing="e" pointcut="within(com.souvc.controller..*)"/>
 </aop:aspect>

 

关键配置代码:

 

<!-- 开启注解扫描 -->
    <context:component-scan base-package="com.souvc" />

    <!-- 支持@RequestMapping请求和Controller映射 -->
    <mvc:annotation-driven />
    <bean
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/" />
        <property name="suffix" value=".jsp" />
    </bean>

    <!-- 声明方面组件--> 
    <bean id="operateLogger" class="com.souvc.aspect.OperateLogger" />

      <!-- 配置AOP -->
    <aop:config>

         <!-- 测试前置通知 -->
        <aop:aspect ref="operateLogger">
            <aop:before method="log1"  pointcut="within(com.souvc.controller..*)"/>
        </aop:aspect>

        <!-- 测试环绕通知 -->
        <aop:aspect ref="operateLogger">
            <aop:around method="log2" pointcut="within(com.souvc.controller..*)"/>
        </aop:aspect>

        <!-- 测试异常通知 -->
         <aop:aspect ref="operateLogger">
            <aop:after-throwing method="log3" throwing="e" pointcut="within(com.souvc.controller..*)"/>
        </aop:aspect>

    </aop:config> 

 

 

 

步骤四:测试

为了便于测试,在EmpController.find()方法中制造一个异常,代码如下:

 

主要代码:

 // 制造一个异常,便于测试异常通知
        //Integer.valueOf("abc");

 

 五、测试效果

 

进入log1()方法
-->用户在2016-04-29 01:12:07,执行了com.souvc.controller.EmpController.find()
执行,find()方法,查询员工数据,发送至列表页面.
-->java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)

 

 

 

4 Spring AOP  后返回通知案例

4.1 问题

使用Spring AOP后返回通知类型。

4.2 方案

Spring AOP使用步骤:

4.3 步骤

实现此案例需要按照如下步骤进行。

 

 

 

 

5 Spring AOP  执行后案例

5.1 问题

使用Spring AOP 执行后通知类型。

5.2 方案

Spring AOP使用步骤:

5.3 步骤

实现此案例需要按照如下步骤进行。

 

步骤一:

在 OperateLogger.java 添加以下代码:

 

 /**
     * 方法名:log5</br>
     * 详述:测试 执行后使用的方法</br>
     * 开发人员:http://www.cnblogs.com/liuhongfeng/  </br>
     * 创建时间:2016年4月29日  </br>
     * @param result
     * @return
     * @throws
      */
    public void log5() {
        // 记录日志
        System.out.println("进入log5()方法");
    }

 

 

步骤二:

在 applicationContext.xml 添加以下代码:

 

<!-- 测试后通知 -->
  <aop:aspect ref="operateLogger">
       <aop:after method="log5"  pointcut="within(com.souvc.controller..*)"/>
  </aop:aspect>

 

步骤三:

运行测试类

 

步骤四:

效果如下:

 

进入log1()方法
-->用户在2016-04-29 01:53:10,执行了com.souvc.controller.EmpController.find()
执行,find()方法,查询员工数据,发送至列表页面.
进入log5()方法
-->java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)

 

 

 

二、 Spring 通过注解形式形式来AOP 来实现前置,环绕,异常通知

 

 

 

Spring AOP相关注解及含义如下:

 

@Aspect:用于声明方面组件

 

@Before:用于声明前置通知

 

@AfterReturning:用于声明后置通知

 

@After:用于声明最终通知

 

@Around:用于声明环绕通知

 

@AfterThrowing:用于声明异常通知

 

 

 

1 Spring AOP注解使用案例

1.1 问题

使用Spring AOP注解替代XML配置,重构上面的3个案例。

1.2 方案

 

分别在对应的方法上面加上注解。

 

1.3 步骤

实现此案例需要按照如下步骤进行。

 

步骤一:开启AOP注解扫描

在applicationContext.xml中,去掉方面组件声明及作用的XML配置,并开启AOP注解扫描,关键代码如下:

 

<!-- 声明方面组件 -->
    <!-- <bean id="operateLogger" class="com.souvc.aspect.OperateLogger" />-->

    <!-- 配置AOP -->

    <!--  <aop:config>
        <aop:aspect ref="operateLogger">
            <aop:before method="log1" pointcut="within(com.souvc.controller..*)"/>
        </aop:aspect>
        <aop:aspect ref="operateLogger">
            <aop:around method="log2"  pointcut="within(com.souvc.controller..*)"/>
        </aop:aspect>
        <aop:aspect ref="operateLogger">
            <aop:after-throwing method="log3" throwing="e" pointcut="within(com.souvc.controller..*)"/>
        </aop:aspect>
     </aop:config>    -->

 

 

<!-- 开启AOP注解扫描 -->
    <aop:aspectj-autoproxy proxy-target-class="true"/>

 

或者:

 

<!-- 启用spring对AspectJ注解的支持 -->  
    <aop:aspectj-autoproxy/>

 

 

步骤二:使用注解声明方面组件

在OperateLogger中,使用@Aspect注解声明方面组件,并分别用@Before、@Around、@AfterThrowing注解声明log1、log2、log3方法,将方面组件作用到目标组件上,代码如下:

 

 

package com.souvc.aspect;

import java.text.SimpleDateFormat;
import java.util.Date;

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.springframework.stereotype.Component;

/**
 * 用于记录日志的方面组件,演示Spring AOP的各种通知类型。
 */
@Component
@Aspect
public class OperateLogger {

    /**
    * 方法名:log1</br>
    * 详述:测试前置通知 </br>
    * 开发人员:http://www.cnblogs.com/liuhongfeng/ </br>
    * 创建时间:2016年4月29日  </br>
    * @throws
     */
     @Before("within(com.souvc.controller..*)")
    public void log1() {
        // 记录日志
        System.out.println("进入log1()方法");
    }



    /**
    * 方法名:log2</br>
    * 详述:环绕通知使用的方法 </br>
    * 开发人员:http://www.cnblogs.com/liuhongfeng/ </br>
    * 创建时间:2016年4月29日  </br>
    * @param p
    * @return
    * @throws Throwable
    * @throws
     */
     @Around("within(com.souvc.controller..*)")
    public Object log2(ProceedingJoinPoint p) throws Throwable {
        // 目标组件的类名
        String className = p.getTarget().getClass().getName();
        // 调用的方法名
        String method = p.getSignature().getName();
        // 当前系统时间
        String date = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss") .format(new Date());
        // 拼日志信息
        String msg = "-->用户在" + date + ",执行了" + className + "." + method + "()";
        // 记录日志
        System.out.println(msg);
        // 执行目标组件的方法
        Object obj = p.proceed();
        // 在调用目标组件业务方法后也可以做一些业务处理
        System.out.println("-->调用目标组件业务方法后...");
        return obj;
    }




   /**
   * 方法名:log3</br>
   * 详述:测试异常通知使用的方法</br>
   * 开发人员:http://www.cnblogs.com/liuhongfeng/  </br>
   * 创建时间:2016年4月29日  </br>
   * @param e
   * @throws
    */
     @AfterThrowing(pointcut = "within(com.souvc.controller..*)", throwing = "e")
    public void log3(Exception e) {
        StackTraceElement[] elems = e.getStackTrace();
        // 将异常信息记录
        System.out.println("-->" + elems[0].toString());
    }


    /**
    * 方法名:log4</br>
    * 详述:测试 返回后通知使用的方法</br>
    * 开发人员:http://www.cnblogs.com/liuhongfeng/  </br>
    * 创建时间:2016年4月29日  </br>
    * @param result
    * @return
    * @throws
     */
     @AfterReturning(value="execution(* com.souvc.controller.*.*(..))",returning="result")
    public void log4(JoinPoint joinPoint,Object result ){
        Object object = joinPoint.getSignature();//方法返回值

        System.out.println("joinPoint.getKind():"+ joinPoint.getKind());
        System.out.println("joinPoint.getTarget():"+joinPoint.getTarget());
        System.out.println("joinPoint.getThis():"+joinPoint.getThis());
        System.out.println("joinPoint.getArgs():"+joinPoint.getArgs().length);
        Object [] args=joinPoint.getArgs();
        for (int i = 0; i < args.length; i++) {
            System.out.println("参数:"+args[i]);
        }
        System.out.println("joinPoint.getSignature():"+joinPoint.getSignature());
        System.out.println("joinPoint.getSourceLocation():"+joinPoint.getSourceLocation());
        System.out.println("joinPoint.getStaticPart():"+joinPoint.getStaticPart());

        String rightnow=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date());
        System.out.println(rightnow+"执行了【"+object+"方法正常执行结束......】"+"【返回结果:"+result+"】"); 
    }



    /**
     * 方法名:log5</br>
     * 详述:测试 执行后使用的方法</br>
     * 开发人员:http://www.cnblogs.com/liuhongfeng/  </br>
     * 创建时间:2016年4月29日  </br>
     * @param result
     * @return
     * @throws
      */
//    @After(value="execution(com.souvc.controller.*.*(..))")
//    public void log5() {
//        // 记录日志
//        System.out.println("进入log5()方法");
//    }



}

 

 

 

步骤三:测试

执行测试方法TestEmpController.test1(),结果如下:

 

无异常的时候:

-->用户在2016-04-27 11:30:22,执行了com.souvc.controller.EmpController.find()
-->记录用户操作信息
查询员工数据,发送至列表页面.
-->调用目标组件业务方法后...

 

 有异常的时候:

-->用户在2016-04-27 11:32:27,执行了com.souvc.controller.EmpController.find()
-->记录用户操作信息
查询员工数据,发送至列表页面.
-->java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)

 

 

 

1)JoinPoint 

java.lang.Object[] getArgs():获取连接点方法运行时的入参列表; 
Signature getSignature() :获取连接点的方法签名对象; 
java.lang.Object getTarget() :获取连接点所在的目标对象; 
java.lang.Object getThis() :获取代理对象本身; 

2)ProceedingJoinPoint 

ProceedingJoinPoint继承JoinPoint子接口,它新增了两个用于执行连接点方法的方法: 
java.lang.Object proceed() throws java.lang.Throwable:通过反射执行目标对象的连接点处的方法; 
java.lang.Object proceed(java.lang.Object[] args) throws java.lang.Throwable:通过反射执行目标对象连接点处的方法,不过使用新的入参替换原来的入参。  

 

 

 

本文作者:souvc

本文出处: 

 

 

 

 

其他的博文可以参考:

3幅图让你了解Spring AOP

Spring Aop实例之xml配置

Spring Aop实例之AspectJ注解配置

 

本文由必威发布于操作系统,转载请注明出处:自定义注解必威,Aspect方式记录日志