spring aop切面的使用 前置 环绕和异常

什么是aop,为什么需要aop

AOP:aspect oriented parogramming面向切面编程,是对所有对象或者是某类对象编程,核心:在不增加代码的基础上还增加新功能
1就是为了方便,看一个国外很有名的大师说,编程的人都是“懒人”,因为他把自己做的事情都让程序做了。用了aop能让你少写很多代码,这点就够充分了吧
2就是为了更清晰的逻辑,可以让你的业务逻辑去关注自己本身的业务,而不去想一些其他的事情,这些其他的事情包括:安全,事物,日志等。

汇编:面向机器
C语言:面向过程,系统软件(防火墙,操作系统,数据库)大都是用C语言写的
Java语言:面向对象---->(面向切面aop)

aop相关概念

切点:定义拦截哪些方法
切面:定义对切点执行哪些操作
而这这些操作有可以分为:
前置通知:在方法执行前进行操作,切面类不调用切点的方法
后置通知:在方法执行后进行操作,切面类不调用切点的方法
环绕通知:在方法执行前后均有操作,切面类会调用切点的方法(如joinPoint.proceed())
所以:前置通知 + 后置通知  != 环绕通知
异常通知:仅仅处理有异常抛出的方法

aop实例-前置通知和自定义注解实现日志功能

1)自定义注解

@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
    /** 要执行的操作类型比如:add操作 **/
    public String operationType() default "";

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


2)定义操作切面的实现类

/**
 * 功能描述: 日志记录AOP实现
 */
public class LogAspect {
    private static LogBusiness logBusiness;
    static {
        if (logBusiness == null) {
            logBusiness = ContextLoader.getCurrentWebApplicationContext().getBean(LogBusiness.class);
        }
    }

    public void beforeAdvice(JoinPoint joinPoint) {
        try {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                    .getRequest();
            
            Signature signature = joinPoint.getSignature();

            String methodName = signature.getName();
            MethodSignature methodSignature = (MethodSignature) signature;
            // 被拦截的方法
            Method method = methodSignature.getMethod();
            //获取被拦截方法上面的 @Log注解的内容
            if (method.getAnnotation(Log.class) == null) {
                return;
            }

            if (parameterTypes != null && arguments != null && parameterTypes.length == arguments.length) {
                //获取被拦截方法上面的 @Log注解的内容值
                operationType = method.getAnnotation(Log.class).operationType();
                operationDesc = method.getAnnotation(Log.class).operationDesc();

                AdminOperationLog log = new AdminOperationLog();
                log.setOperationType(operationType);
                  ******
                log.setOperateTime(DateUtil.formatDate(new Date(), Constant.DATE_FORMAT_PATTENER));
                logBusiness.addLog(log);
            }

        } catch (Exception e) {
            // 记录本地异常日志
            LOGGER.error("记录日志异常信息:{}", e);
        }

    }

    /**
     * 功能描述:删除不需要的参数项
     */
    private void removeUnNeedArgs(List<Object> args) {
    }

}

3)调用实例

@Log(operationType =“123”, operationDesc="wangwenjun")  
    public void do(String[] args) {  
        System.out.println("hello");  
    }  
}

4)在配置文件中设置切入点和切面类
     

<!--注意:proxy-target-class属性值决定是基于接口的还是基于类的代理被创建。如果proxy-target-class 属性值被设置为true,那么基于类的代理将起作用(这时需要cglib库)。如果proxy-target-class属值被设置为false或者这个属性被省略,那么标准的JDK 基于接口的代理将起作用。即使你未声明 proxy-target-class="true" ,但运行类没有继承接口,spring也会自动使用CGLIB代理。高版本spring自动根据运行类选择 JDK 或 CGLIB 代理。-->
     <!--<aop:aspectj-autoproxy proxy-target-class="true"/>-->

     <!—定义bean-->
     <bean id="logAspect" class="com.suning.gcpm.admin.util.log.LogAspect"></bean>
    <!—定义切面信息-->
     <aop:config>
        <!-- 切面类 -->
        <aop:aspect  ref="logAspect">
        <!-- 切入点 -->
        <aop:pointcut expression="execution (* com.suning.gcpm.admin.web.controller..*.*(..))" id="myPointcut" />
        <!-- 在方法执行前 执行切入的方法 -->    
        <aop:before method="beforeAdvice" pointcut-ref="myPointcut" />
         <!-- 在方法执行后 执行切入的方法 --> 
        <!--<aop:after method="afterAdvice" pointcut-ref="myPointcut" /> --> 
        </aop:aspect>
    </aop:config>


1.@aspect:表明是切面类
2.@Pointcut(value = "execution (* com.cms.admin.web.controller.*.*(..))"):定义统一拦截的切入点,其中第一个*表示任意返回类型,第二个*表示任意类名,假如第二个*前面是两个.则表示包括包里面的子包,第三个*表示任意方法名,后面的小括号表示任意参数值
3.@Before("controllerAspect()"):表示拦截方法执行前的动作
注意:文件中的aop的配置必须在service的实例化之后,即aop的配置切入点对象注入配置之后,aop的相关配置才会有效,否则无效。

异常通知:实现的接口是ThrowsAdvice

说明:该实例借鉴与https://www.cnblogs.com/sjcq/p/7450192.html

定义异常类

package com.apt.study.exception;

public class StudyException extends RuntimeException {

    private static final long serialVersionUID = -6183216129830888521L;

    /**
     * 异常信息
     */
    protected String message;
    
    /**
     * 异常编码
     */
    protected int code;
    
    public static final StudyException PARAM_VALIDATE_EXCEPTION = new StudyException(1234,"参数校验出错");
    
    public StudyException(int code, String msgFormat, Object... args) {
        super(String.format(msgFormat, args));
        this.code = code;
        this.message = String.format(msgFormat, args);
    }
    
    public StudyException(String message, Throwable cause) {
        super(message, cause);
    }

    public StudyException(Throwable cause) {
        super(cause);
    }

    public StudyException(String message) {
        super(message);
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public int getCode() {
        return code;
    }    public void setCode(int code) {
        this.code = code;
    }
}


定义异常处理类

package com.apt.study.exception;

import java.lang.reflect.Method;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.ThrowsAdvice;

public class ExceptionLogHandler implements ThrowsAdvice{

    public Logger logger = LoggerFactory.getLogger(ExceptionLogHandler.class);
    
    public void afterThrowing(Method method, Object[] args, Object target, StudyException ex) {
        logger.error("ExceptionLogHandler--StudyExcception");
        logger.info("-------errCode:" + ex.getCode() + "; errMessage:" + ex.getMessage());
        errorClassInfo(ex);
        logger.info("-------" + ex.fillInStackTrace());
    }
    
    public void afterThrowing(Method method, Object[] args, Object target, Exception ex) {
        
        logger.error("ExceptionLogHandler--Exception");
        logger.error("------->Erro Class: "+ target.getClass().getName());
        logger.error("------->Error method:"+ method.getName());
        
        if(args != null) {
            for(int i=0; i<args.length; i++) {
                logger.error("------->args[" + i + "]: " + args[i]);
            }
        }
        
        logger.error("------->Exception class: " + ex.getClass().getName());
        errorClassInfo(ex);
        logger.error("------->" + ex.fillInStackTrace());
    }
    
    //打印抛出异常地方的信息
    private void errorClassInfo(Exception ex) {
        /*
         * ex.getStackTrace()返回堆栈跟踪元素的数组,每个元素表示一个堆栈帧。数组的第零个元素(假定数据的长度为非零)表示堆栈顶部,
         * 它是序列中最后的方法调用。
         * 通常,这是创建和抛出该 throwable 的地方。数组的最后元素(假定数据的长度为非零)表示堆栈底部,它是序列中第一个方法调用
         */
        StackTraceElement[] stackTraceElementArr= ex.getStackTrace();
        StackTraceElement stackTraceElement = stackTraceElementArr[0];
        logger.error("------->Erro File:" + stackTraceElement.getFileName());
        logger.error("------->Erro Method:" + stackTraceElement.getMethodName());
        logger.error("------->Erro Line:" + stackTraceElement.getLineNumber());
    }
}


application-context.xml的配置

<!-- 异常信息 拦截 -->
<bean id="exceptionLog" class="com.apt.study.exception.ExceptionLogHandler"></bean>  
<aop:config>
   <aop:pointcut id="exceptionTrade" expression="execution(* com.apt.study.service.*.*(..))" />
   <aop:advisor pointcut-ref="exceptionTrade" advice-ref="exceptionLog"/>
</aop:config>

环绕通知实现记录每个方法的执行时间

定义实现MethodInterceptor的切面类

package org.cboard.services.aop;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @ClassName: LogAspect
 * @Description: 日志记录AOP实现
 */
public class LogAspect implements MethodInterceptor {
    private static Logger logger = LoggerFactory.getLogger(LogAspect.class);
    /**
     * desc: 增加日志:被拦截方法的执行时间
     */
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        StopWatch clock = new StopWatch();
        clock.start(); //计时开始
        Object result = invocation.proceed();
        clock.stop();  //计时结束

        //方法参数类型,转换成简单类型
        Class[] params = invocation.getMethod().getParameterTypes();
        String[] simpleParams = new String[params.length];
        for (int i = 0; i < params.length; i++) {
            simpleParams[i] = params[i].getSimpleName();
        }
        logger.info("Takes:" + clock.getTime() + " ms ["
                + invocation.getThis().getClass().getName() + "."
                + invocation.getMethod().getName() + "(" + StringUtils.join(simpleParams, ",") + ")] ");

        return result;
    }
}

spring.xml中配置

    <bean id="logAspect" class="org.cboard.services.aop.LogAspect"></bean>
    <aop:config>
        <aop:advisor id="logPoint" advice-ref="logAspect" pointcut="execution(* org.cboard..*.*(..))"/>
        <!--<aop:advisor id="methodTimeLog" advice-ref="methodTimeAdvice" pointcut="execution(* *..service..*(..))"/>-->
    </aop:config>

多个aop的坑

<bean id="dataSourceAspect" class="com.ruisitech.datasource.DataSourceAspect"></bean>
<!--定义切面信息-->
<aop:config>
    <!-- 切面类 -->
    <aop:aspect  ref="dataSourceAspect">
        <!-- 切入点 -->
        <aop:pointcut expression="execution (* com.ruisitech.bi.service..*.*(..))" id="myPointcut" />
        <!-- 切入的方法 -->
        <aop:before method="beforeAdvice" pointcut-ref="myPointcut" />
        <aop:after method="afterAdvice" pointcut-ref="myPointcut" />
    </aop:aspect>
</aop:config>


<bean id="jobLockAspect" class="com.ruisitech.bi.job.manage.JobLockAspect"></bean>
<!--定义切面信息-->
<aop:config>
    <!-- 切面类 -->
    <aop:aspect  ref="jobLockAspect">
        <!-- 切入点 -->
        <aop:pointcut expression="execution (* com.ruisitech.bi.job..*.*(..))" id="myPointcut" />
        <!-- 切入的方法 -->
        <aop:around method="cacheLockPoint" pointcut-ref="myPointcut" />
    </aop:aspect>
</aop:config>

如果代码中用到了多个aop,并且有多个的配置,如上面的配置,注意两个aop配置中切点id相同,都是myPointcut,那上面DataSourceAspect对应切入点不再是com.ruisitech.bi.service包,而是com.ruisitech.bi.job包,很奇怪,应该是切入点名称相同时,以后来者为准,所以id为myPointcut的切入点对应的包最终对应为com.ruisitech.bi.job包。
但是因为 id

解决:
将两个切入点id修改为不同即可。

aop用好了简化代码,用不好采坑,看下面三个坑:

见:https://blog.csdn.net/h2604396739/article/details/102610610

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值