菜瓜:你觉得AOP是啥

水稻:我觉得吧,AOP是对OOP的补充。通常情况下,OOP代码专注功能的实现,所谓面向切面编程,大多数时候是对某一类对象的方法或者功能进行增强或者抽象

菜瓜:我看你这个理解就挺抽象的

水稻:举个栗子!我要在满足开闭原则的基础下对已有功能进行扩展

  • 我现在想对很多个功能增加日志功能,但是代码已经打好包了,不想改。又或者有时候方法调用很慢,想定位问题
  • low一点的方法就是每个方法调用之前记录调用开始,之后记录调用结束

菜瓜:你说的这个low一点的方法怎么好像是在说我???

水稻:建议看一下动态代理设计模式【DP-动态代理】JDK&Cglib,我当然知道你不会看,所以我还准备了自定义注解的栗子

  • package com.hb.merchant.config.aop;
    
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.EnableAspectJAutoProxy; /**
    * @author QuCheng on 2020/6/23.
    */
    @Configuration
    @EnableAspectJAutoProxy
    public class AopConfig {
    } package com.hb.merchant.config.aop; import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target; /**
    * @author QuCheng on 2020/6/23.
    */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface OperatorLog {
    } package com.hb.merchant.config.aop; import lombok.extern.slf4j.Slf4j;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.springframework.stereotype.Component; /**
    *
    * @author QuCheng on 2020/6/23.
    */
    @Aspect
    @Component
    @Slf4j
    public class OperatorAspect { @Around("@annotation(OperatorLog)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    //获取要执行的方法
    MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
    //记录方法执行前日志
    log.info("startLog: {} 开始了。。。" , methodSignature.getName());
    //获取方法信息
    String[] argNames = methodSignature.getParameterNames();
    // 参数值:
    final Object[] argValues = joinPoint.getArgs();
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < argNames.length; i++) {
    String value = argValues[i] == null ? "null" : argValues[i].toString();
    sb.append(argNames[i]).append("=").append(value).append(",");
    }
    String paramStr = sb.length() > 0 ? sb.toString().substring(0, sb.length() - 1) + "]" : "";
    log.info("参数信息为:[{}", paramStr); //执行方法
    Object result;
    try {
    result = joinPoint.proceed();
    } catch (Exception e) {
    log.error("errorLog", e);
    return null;
    } //记录方法执行后日志
    log.info("endLog: {} 结束了。。。" , methodSignature.getName());
    return result;
    } } package com.hb.merchant.controller.icbc.item.oc; import com.hb.merchant.config.aop.OperatorLog;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.util.Assert;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController; /**
    * @author QuCheng on 2020-06-23.
    */
    @RestController
    @RequestMapping("/item")
    @Slf4j
    public class ItemOcController { @OperatorLog
    @GetMapping("/delete")
    public String delete(Long itemId) {
    Assert.notNull(itemId,"itemId不能为空");
    return "delete finished ...";
    }
    } // 后台打印
    startLog: delete 开始了。。。
    参数信息为:[itemId=1]
    endLog: delete 结束了。。。

菜瓜:这个自定义注解又是怎么实现的呢?

水稻:不愧是你,没有源码看来是满足不了你的好奇心了!!不知道你是否还记得我们之前有聊到过bean创建完毕后会调用一些PostProcessor对其进一步操作

菜瓜:有印象,@PostConstruct注解就是InitDestroyAnnotationBeanPostProcessor在这里调用的,还自定义过BeanPostProcessorT对象打印输出过bean信息

水稻:你猜Spring是怎么操作的

菜瓜:let me try try。结合刚刚的栗子和提示,大胆猜测应该是用PostProcessor在bean创建完成之后生成代理对象。实际调用代理的invoke方法实现对被代理bean的增强

水稻:思路正确。看脉络

  • 入口在AbstractAdvisorAutoProxyCreator#initializeBean
  • protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
    。。。
    // BeanNameAware BeanFactoryAware ...
    invokeAwareMethods(beanName, bean);
    。。。
    // BeanPostProcessorBefore @PostConstruct
    wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
    。。。
    // initMethod InitializingBean接口
    invokeInitMethods(beanName, wrappedBean, mbd);
    。。。
    if (mbd == null || !mbd.isSynthetic()) {
    // aop
    wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
    }
    return wrappedBean;
    }
  • 从aop入口跟下去
  • protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    。。。
    // 收集切面信息匹配被代理对象
    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
    if (specificInterceptors != DO_NOT_PROXY) {
    this.advisedBeans.put(cacheKey, Boolean.TRUE);
    // 如果符合切面 创建代理,被代理对象被代理引用
    Object proxy = createProxy(
    bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
    this.proxyTypes.put(cacheKey, proxy.getClass());
    return proxy;
    } this.advisedBeans.put(cacheKey, Boolean.FALSE);
    return bean;
    }
  • 跟createProxy方法 -> DefaultAopProxyFactory#createAopProxy
  • @Override
    public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
    Class<?> targetClass = config.getTargetClass();
    if (targetClass == null) {
    throw new AopConfigException("TargetSource cannot determine target class: " +
    "Either an interface or a target is required for proxy creation.");
    }
    if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
    // jdk动态代理类
    return new JdkDynamicAopProxy(config);
    }
    // cglib
    return new ObjenesisCglibAopProxy(config);
    }
    else {
    return new JdkDynamicAopProxy(config);
    }
    }
  • 此处省略了切面类搜集和匹配的过程。可以简单理解成搜集到所有的切面类信息获取pointcut的目录或者注解信息,匹配当前bean是否属于pointcut目标范围
  • 另外我们可以看到最后返回的bean已经不是原始bean了,而是代理对象。也就是说getBean("xxx")返回的对象实际是代理对象,被代理对象被其成员变量直接引用

菜瓜:然后代理类中都有invoke方法,那些advice(@Around,@Before...)在invoke中找到适当时机调用对吧

水稻:是的,这里我想结合@Transactional注解会更容易理解,你肯定用过这个注解吧,它其实。。。

菜瓜:停。。。今天获取的知识量已经够了,我下去自己断点走一趟再熟悉熟悉。下次请结合Transactional注解再敲打我吧

水稻:也好,我下去再给你准备几个栗子

总结:

  • AOP提供了在不侵入代码的前提下动态增强目标对象的途径,让OOP更加专注于实现自己的逻辑
  • 而Spring的实现还是老套路,利用PostProcessor在类初始化完成之后替需要的bean创建代理对象
  • 这里还有一些细节没有照顾到,譬如说AOP解析类是什么时候注册到IOC容器的(偷偷告诉你从@EnableAspectJAutoProxy注解下手)

【Spring】AOP的代理默认是Jdk还是Cglib?的更多相关文章

  1. 死磕Spring之AOP篇 - Spring AOP自动代理(二)筛选合适的通知器

    该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读. Spring 版本:5.1 ...

  2. 死磕Spring之AOP篇 - Spring AOP自动代理(三)创建代理对象

    该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读. Spring 版本:5.1 ...

  3. 死磕Spring之AOP篇 - Spring AOP自动代理(一)入口

    该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读. Spring 版本:5.1 ...

  4. Hibernate 延迟加载的代理模式 和 Spring AOP的代理模式

    Hibernate 延迟加载的代理模式 和 Spring AOP的代理模式 主题 概念 Hibernate 延迟加载的代理模式 Spring AOP的代理模式 区别和联系 静态代理和动态代理 概念 代 ...

  5. Spring AOP动态代理实现,解决Spring Boot中无法正常启用JDK动态代理的问题

    Spring AOP底层的动态代理实现有两种方式:一种是JDK动态代理,另一种是CGLib动态代理. JDK动态代理 JDK 1.3版本以后提供了动态代理,允许开发者在运行期创建接口的代理实例,而且只 ...

  6. 什么是 Spring AOP 和代理

    https://mbd.baidu.com/newspage/data/landingsuper?context=%7B%22nid%22%3A%22news_9403056301388627935% ...

  7. 求求你,下次面试别再问我什么是 Spring AOP 和代理了!

    https://mbd.baidu.com/newspage/data/landingsuper?context=%7B%22nid%22%3A%22news_9403056301388627935% ...

  8. Spring AOP 动态代理 缓存

    Spring AOP应用:xml配置及注解实现. 动态代理:jdk.cglib.javassist 缓存应用:高速缓存提供程序ehcache,页面缓存,session缓存 项目地址:https://g ...

  9. spring aop 动态代理批量调用方法实例

    今天项目经理发下任务,需要测试 20 个接口,看看推送和接收数据是否正常.因为对接传输的数据是 xml 格式的字符串,所以我拿现成的数据,先生成推送过去的数据并存储到文本,以便验证数据是否正确,这时候 ...

随机推荐

  1. Alink漫谈(五) : 迭代计算和Superstep

    Alink漫谈(五) : 迭代计算和Superstep 目录 Alink漫谈(五) : 迭代计算和Superstep 0x00 摘要 0x01 缘由 0x02 背景概念 2.1 四层执行图 2.2 T ...

  2. JavaScript几种继承方式的总结

    1.原型链继承 直接将子类型的原型指向父类型的实例,即"子类型.prototype = new 父类型();",实现方法如下: //父类构造函数 function father(n ...

  3. Java实现 LeetCode 26 删除排序数组中的重复项

    26. 删除排序数组中的重复项 给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度. 不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) ...

  4. 第五届蓝桥杯JavaA组国(决)赛真题

    解题代码部分来自网友,如果有不对的地方,欢迎各位大佬评论 题目1.海盗分金币 有5个海盗,相约进行一次帆船比赛. 比赛中天气发生突变,他们被冲散了. 恰巧,他们都先后经过途中的一个无名的荒岛,并且每个 ...

  5. SQL Server账号密码(sa)登录失败 错误原因:18456

    (其实以前经常用的时候,都很简单,最近一段时间不用了,再一看发现都忘记的差不多了,还是写一篇博客吧,防止下一次再在这种问题上面浪费时间) 使用window登录 打开属性 打开安全性 选择SQL ser ...

  6. Java实现第九届蓝桥杯分数

    分数 题目描述 1/1 + 1/2 + 1/4 + 1/8 + 1/16 + - 每项是前一项的一半,如果一共有20项, 求这个和是多少,结果用分数表示出来. 类似: 3/2 当然,这只是加了前2项而 ...

  7. Python UI自动化测试实操

    本UI 自动化框架主要的实验的目的是:完成了登录页面的自动化登录与打开会员中心的页面这一自动化的过程. 废话不多说,直接上代码截图: 我们首先来看看整个工程的目录结构,这样以便于了解项目的调用关系: ...

  8. react 性能优化注意事项

    工具: React 16 或更新版本   只需在url 后边加  ?react_perf 后 performance 一栏中会添加 User Timing devtool 分析 state.props ...

  9. Redis学习笔记(十六) Sentinel(哨兵)(下)

    消失了一段时间,我又回来啦.不多说,继续把哨兵看完. 检测主观下线状态 默认情况下,Sentinel会以每秒一次的频率向所有与他创建了命令连接的实例(主从服务器以及其他Sentinel)发送PING命 ...

  10. python3 源码阅读-虚拟机运行原理

    阅读源码版本python 3.8.3 参考书籍<<Python源码剖析>> 参考书籍<<Python学习手册 第4版>> 官网文档目录介绍 Doc目录主 ...