重新认识AOP(二)

1.使用场景

      项目中比如日志,事物,方法性能统计,自定义链路监控等场景都需要在方法的执行前后加一段代码处理逻辑,如果都在业务类里进行写的话,代码会出现很多冗余重复的代码.比如下面这种在方法执行前后增加日志,考虑采用插桩机制避免这种重复代码

   public void testInfo(String param){
        log.info("before invoke, args:{}", param);
        String result=getResult();
        log.info("after invoke, result:{}", result);
    }
    
    public static String getResult(){
        return "ok";
    }

2.插桩机制原理

    我们可以在编译期,编译后,以及运行期都可以利用插桩机制来对代码增强,下图出在世杰欧巴的总结

 

  2.1 编译时

  当一个类文件被编译时进行织入(AspectJ的织入编译器),使用编译器,基于注解处理器来进行对源代码增强成对应的字节码文件,具体流程如下 :

2.2 类加载时

   使用特殊的ClassLoader在目标类被加载到程序之前增强类的字节代码,比如利用byte-buddy进行对代码增强,具体参考下面

https://github.com/raphw/byte-buddy
https://github.com/diguage/byte-buddy-tutorial
https://blog.csdn.net/qyongkang/article/details/7799603 -javaagent参数使用

https://www.jianshu.com/p/b72f66da679f  基于Java Instrument的Agent实现

   <dependency>
            <groupId>net.bytebuddy</groupId>
            <artifactId>byte-buddy</artifactId>
            <version>${bytebuddy.version}</version>
        </dependency>
        <dependency>
            <groupId>net.bytebuddy</groupId>
            <artifactId>byte-buddy-agent</artifactId>
            <version>${bytebuddy.version}</version>
            <scope>test</scope>
        </dependency>

2.3 运行期

 运行期我们经常用的就是Spring Aop,一般通过配置文件,aspect注解,DefaultPointcutAdvisor这3种方式进行增强

 <bean id="tx" class="com.jk.service.AopTestManager" />
    <aop:config>
        <aop:aspect ref="tx">
            <aop:pointcut id="placeOrder"
                          expression="execution(* com.jk.service.*.testAop(..))" />
            <aop:before pointcut-ref="placeOrder" method="start" />
            <aop:after-returning pointcut-ref="placeOrder"	method="commit" />
            <aop:after-throwing pointcut-ref="placeOrder" method = "rollback"/>
        </aop:aspect>
    </aop:config>


@Aspect
public class CustomAspect  {

    @Pointcut("execution(public * com.jk.service.TestService.getDemo(..))")
    public void target1(){
    }

    /**
     * 使用方法拦截可以,使用注解不行
     */
    @Before("target1()")
    public void before() {
       //do what you want
    }
}


@Configuration
public class SimpleConfig {

    //public static final String traceExecution = "execution(public * com.jk.service.TestService.getDemo(..))";

    @Bean
    public DefaultPointcutAdvisor loggableAnnotationClassPointCut() {
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
        advisor.setOrder(Ordered.HIGHEST_PRECEDENCE+500);
        AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(Loggable.class, true);
        LoggableInterceptor interceptor = new LoggableInterceptor();
       //AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
       //pointcut.setExpression(traceExecution);
        advisor.setPointcut(pointcut);
        advisor.setAdvice(interceptor);
        return advisor;
    }

}


public class LoggableInterceptor implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Logger proxyLogger;
        Loggable loggable = AnnotatedElementCacheUtils.getMergedAnnotation(invocation.getThis().getClass(), Loggable.class);
        if (loggable == null || !loggable.enabled()) {
            return invocation.proceed();
        }
        String methodName = invocation.getMethod().getName();
        if (StringUtils.isEmpty(loggable.value())) {
            proxyLogger = LoggerFactory.getLogger(invocation.getThis().getClass());
        } else {
            proxyLogger = LoggerFactory.getLogger(loggable.value());
        }

        String argsJson = null;

        try {

            if (loggable.argsLogLevel().equals(Loggable.Level.INFO)) {
                    argsJson = JacksonUtil.writeValueAsString(invocation.getArguments());
                    proxyLogger.info("before invoke, method:{}, args:{}", methodName, argsJson);
            } 

            Object result = invocation.proceed();

            if (loggable.argsLogLevel().equals(Loggable.Level.INFO)) {
                    argsJson = JacksonUtil.writeValueAsString(invocation.getArguments());
                    proxyLogger.info("after invoke, method:{}, args:{},", methodName, argsJson);
            } 
            return result;
        } catch (Throwable cause) {
            if (argsJson == null) {
                argsJson = JacksonUtil.writeValueAsString(invocation.getArguments());
            }
            proxyLogger.error("invoke fail, method:{}, args:{}", methodName, argsJson, cause);
            throw cause;
        }
    }

}


详细使用方式可参以下博客https://blog.csdn.net/u013905744/article/details/91364736

3 源代码分析 

3.1概念介绍

  进行源代码分析之前,先回顾下spring的基本概念,详细参考官网https://docs.spring.io/spring/docs/5.2.5.RELEASE/spring-framework-reference/core.html#spring-core

Aspect(切面):通常是一个类,里面可以定义切入点和通知;比如AspectComponentDefinition
JointPoint(连接点):程序执行过程中明确的点,指目标方法的执行
Advice(通知或增强):AOP在特定的切入点上执行的增强处理,可以是前置处理、后置处理、异常处理等;对应的处理类为AspectJAfterAdvice,AspectJAfterReturningAdvice,AspectJAfterThrowingAdvice,AspectJAroundAdvice,AspectJMethodBeforeAdvice,类图如下,这几个类实现了接口MethodInterceptor,而MethodInterceptor最终继承父接口Advice接口(我理解advice接口是目标方法增强的意思,既在方法执行前后等进行增强)


(4)Pointcut(切入点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式;如AspectJExpressionPointcut类,类图如下,最重要实现这2个接口的实现方法Pointcut#getMethodMatcher,MethodMatcher#matches,Spring常用的Pointcut
包括下面几个NameMatchMethodPointcut;JdkRegexpMethodPointcut;AspectJExpressionPointcut;AnnotationMatchingPointcut;ComposablePointcut

    3.2 基于xml配置的源码分析

springboot的版本2.0.4.RELEASE,spring版本是5.0.8RELEASE,Spring aop核心概念aspect、pointcut、advisor,最终都会解析为BeanDefinition,

    3.2.1读取xml开始到BeanDefinition

    3.2.2 Bean初始化后回调接口BeanPostProcessor

       实现类AspectJAwareAdvisorAutoProxyCreator主要是在初始化后判断是否是需要对point-cut表达式提及的类以及方法进行代理操作,默认采取JDK代理,如果指定proxy-target-class属性为true,则也会采用CGLIB代理,划重点:很多公共组件都是实现BeanPostProcessor对spring容器的bean做特殊处理的,比如我们常用的注解属性加载AutowiredAnnotationBeanPostProcessor类,时序图如下:

3.2.3 代理对象执行拦截

既JDK生成的代理对象或cglib生成的代理对象,在执行目标方法的时候进行增强

  其中ReflectiveMethodInvocation链式调用设计的非常巧妙,可以本地debug下领会下

 private int currentInterceptorIndex = -1;

  public Object proceed() throws Throwable {
        if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
            return this.invokeJoinpoint();
        } else {
            Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
            if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
                InterceptorAndDynamicMethodMatcher dm = (InterceptorAndDynamicMethodMatcher)interceptorOrInterceptionAdvice;
                return dm.methodMatcher.matches(this.method, this.targetClass, this.arguments) ? dm.interceptor.invoke(this) : this.proceed();
            } else {
                return ((MethodInterceptor)interceptorOrInterceptionAdvice).invoke(this);
            }
        }
    }

4 后记

  上面3简单介绍了这个源码的运行流程,大家也可以用DefaultPointcutAdvisor来实现aop,在本地简单搭建个环境,从Definition到getBean,再到代理对象的生成(利用BeanPostProcessor回调)进行调试一下,然后分析下源码,类图以及时序图,因为这些道理大家一看就懂,但是如果不自己总结下,看了经常忘,多总结,多学习,多看看身边大牛,如果遇到问题或者新的知识点,大牛是怎么处理的怎么学习的。多思考,才能学得更多

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值