所谓AOP就是面向切面编程,简单的说就是在原来的方法基础上通过AOP切面对该方法进行增强。

一、Spring默认不支持@AspectJ风格的切面声明,为了支持需要在配置文件中添加如下配置:

<aop:aspectj-autoproxy/>

如下所示:

项目中 spring切面记录日志性能降低吗 spring aop日志切面_日志管理

注:以上配置如果在controller层和service层中都用到,那么都需要在controller的配置和spring主配置都添加,如果不添加的效果在下面会有讲解。

二、定义切面aspect

1、定义切入面:在所定义的类中使用注解@Aspect即表示该类作为切面,如:

项目中 spring切面记录日志性能降低吗 spring aop日志切面_aop_02

2、定义切入点:在这里我使用的是注解的方式(也可以使用较常用的通配符方式)

项目中 spring切面记录日志性能降低吗 spring aop日志切面_aspect_03

2.1 定义注解:

项目中 spring切面记录日志性能降低吗 spring aop日志切面_aspect_04

3、定义通知:根据业务需求自己定义,在这里我使用的是后置通知:即表示的是在使用该注解@LogController方法执行完之后再执行此切面方法

项目中 spring切面记录日志性能降低吗 spring aop日志切面_spring_05

getControllerMethodDescription()

/**
     * 获取注解中对方法的描述信息 用于Controller层注解
     * 
     * @param joinPoint 切入点
     * @return 方法描述
     * @throws Exception
     */
    @SuppressWarnings("rawtypes")
    private Map<String, String> getControllerMethodDescription(JoinPoint joinPoint) throws Exception {
        Map<String, String> result = new HashMap<String, String>();
        //类名
        String targetName = joinPoint.getTarget().getClass().getName();
        //方法名称
        String methodName = joinPoint.getSignature().getName();
        //获取入参列表
        Object[] arguments = joinPoint.getArgs();
        //类对象
        Class<?> targetClass = Class.forName(targetName);
        Method[] methods = targetClass.getMethods();
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                Class[] clazzs = method.getParameterTypes();
                if (clazzs.length == arguments.length) {
                    //获取注解对象
                    LogController logController=method.getAnnotation(LogController.class);
                    result.put("logType", logController.logType().toString());
                    result.put("operation", logController.operation());
                    result.put("description", targetName+joinPointStr+methodName+leftJoinStr+getParamsRemark(joinPoint,logController, arguments)+rightJoinStr);
                    break;
                }
            }
        }
        return result;
    }

getParamsRemark()

private String getParamsRemark(JoinPoint joinPoint,LogController logController, Object[] param){
        System.out.println(logController.propertys()
                +"==========");
        //如果没传递参数属性,说明传递的是对象
        if(StringUtils.isBlank(logController.propertys())){
            return getMethodParams(joinPoint);
        }

        if(param ==null || param.length==0){
            param[0]="";
        }
        //获取参数属性  name,age
        String propertys = logController.propertys();
        StringBuffer sb = new StringBuffer();  

        if(propertys == null || "".equals(propertys)){
            return sb.toString();
        }
        //分割获取参数属性名称    name  age
        String[] propertysRemark = propertys.split(splitParaStr);  
        if(propertysRemark == null || propertysRemark.length == 0){
            return sb.toString();
        }

        int count = 0;
        for (int i=0;i<propertysRemark.length;i++) {
            Object name=null;
            if(param[0] instanceof String){
                //表明是多个参数,否则就是对象
                name=param[i];
            }else{
                name=param[0];
            }
            //参数属性对应的值
            String value =  propertysRemark[i];
            if(count == propertysRemark.length){
                sb.append(propertysRemark[i] + splitNameValueStr + getID(name, value) +splitParaStr);  
            }else{
                sb.append(propertysRemark[i] + splitNameValueStr + getID(name, value));
            }
            count++;
            sb.append(splitParaStr);
        }
        return sb.substring(0, sb.length()-1).toString();
    }

getID()

/** 
     * 通过java反射来从传入的参数object里取出我们需要记录的id,name等属性,   
     * 此处我取出的是id 
     */   
    private String getID(Object obj,String param){   
        if(obj instanceof String){   
            return obj.toString();   
        }
        if(obj instanceof Integer){   
            return obj.toString();   
        }
       //构造set和get
        PropertyDescriptor pd = null;   
        Method method = null;   
        String v = "";   
        try{   
            pd = new PropertyDescriptor(param,obj.getClass());   
            method = pd.getReadMethod();     
            v = String.valueOf(method.invoke(obj));    
        }catch (Exception e) {   
            e.printStackTrace();   
        }
        return v;   
    }

getMethodParams()

/**
     * 
     * @描述:获取方法参数
     * @创建人:rz.li
     * @创建时间:2017年7月18日上午9:36:04
     * @param joinPoint
     * @return
     */
    private String getMethodParams(JoinPoint joinPoint) {
        String params = "";
        if (joinPoint.getArgs() != null && joinPoint.getArgs().length > 0) {
            for (int i = 0; i < joinPoint.getArgs().length; i++) {
                Object object = joinPoint.getArgs()[i];
                try {
                    params += JSONUtils.obj2json(object) + ";";
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        return params;
    }

4、开始使用切面:

在Controller层中

项目中 spring切面记录日志性能降低吗 spring aop日志切面_aspect_06

5、测试结果:

项目中 spring切面记录日志性能降低吗 spring aop日志切面_日志管理_07

三、异常信息的处理

1、定义:对于异常信息的AOP,跟上面定义的方式一样,同样使用注解方式,代码如下:

@Pointcut("@annotation(com.demo.annotation.LogService)")
    public void serviceAspect() {}

2、注解:

@Target({ElementType.PARAMETER, ElementType.METHOD})  
@Retention(RetentionPolicy.RUNTIME)  
@Documented  
public @interface LogService {

    /** 要执行的操作类型比如:add操作 **/  
    public LogTypeEnum logType() default LogTypeEnum.SYS_LOG; 

    /** 要执行的具体操作比如:添加用户 **/  
    public String description() default "";
}

3、使用:

使用后置异常通知进行对Service层异常信息的捕获,此时在service方法中不需要try-catch捕获异常信息,如下所示:
@LogService(logType=LogTypeEnum.SYS_LOG,description="测试aop异常捕获")
    @Override
    public void testAop(BatchSellPO b) {
        int i=1/0;
        System.out.println(i);
    }

具体异常处理逻辑

@AfterThrowing(pointcut = "serviceAspect()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
        try {
            //TODO 获取用户的信息
            String userId="123";
            System.out.println("=====异常通知开始=====");
            System.out.println("异常代码:" + e.getClass().getName());
            System.out.println("异常信息:" + e.getMessage());
            System.out.println("异常方法:" + joinPoint.getTarget().getClass().getName()+joinPointStr+joinPoint.getSignature().getName() + "()");
            System.out.println("方法描述:" + getServiceMethodDescription(joinPoint));
            System.out.println("请求人ID:" + userId);
            System.out.println("请求IP:" + request.getRemoteAddr());
            System.out.println("请求参数:" + getMethodParams(joinPoint));
            /*==========数据库日志=========*/  
            SysLog log = new SysLog();

            //保存数据库  
            //sysLogService.insert(log);  
            System.out.println("=====异常通知结束=====");  
        } catch (Exception e2) {
            logger.error(e2.getMessage(), e2);
        }
    }

getServiceMethodDescription()

/**
     * @描述:获取注解中对方法的描述信息 用于Service层注解
     * @创建人:rz.li
     * @创建时间:2017年8月31日上午10:26:16
     * @param joinPoint
     * @return 方法描述
     * @throws Exception
     */
    @SuppressWarnings("rawtypes")
    private Map<String, Object> getServiceMethodDescription(JoinPoint joinPoint) throws Exception {
        Map<String, Object> result = new HashMap<String, Object>();
        String targetName = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        Object[] arguments = joinPoint.getArgs();
        Class<?> targetClass = Class.forName(targetName);
        Method[] methods = targetClass.getMethods();
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                Class[] clazzs = method.getParameterTypes();
                if (clazzs.length == arguments.length) {
                    result.put("logType", method.getAnnotation(LogService.class).logType());
                    result.put("description", method.getAnnotation(LogService.class).description());
                    break;
                }
            }
        }
        return result;
    }

4、测试
在访问的时候没进入代理方法,看异常信息发现默认使用的是jdk代理(接口代理),而AOP切面的注解时基于类的代理,故改为cglib代理(类代理),由此可判断在spring主配置文件中没有添加

<aop:aspectj-autoproxy proxy-target-class="true"/>

项目中 spring切面记录日志性能降低吗 spring aop日志切面_日志管理_08

添加配置之后测试结果如下:

项目中 spring切面记录日志性能降低吗 spring aop日志切面_aspect_09

说明异常切面已成功!