使用

代理模式 是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。按照代理的创建时期,代理类可以分为两种。

  1. 静态代理:

原理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。

原理:在编译期,切面直接以字节码形式编译到目标字节码文件中
优缺点:对系统性能无影响但是不够灵活

2.动态AOP

  1. 动态代理

原理:在运行期,目标类加载,通过反射机制为接口动态生成代理类。将切面织入到代理类中
优缺点:更灵活,但是切入的关注点要实现接口,如果没有实现接口则不能使用JDK代理。使用反射大量生成类文件可能引起Full GC造成性能影响,因为字节码文件加载后会存放在JVM运行时区的方法区(或者叫持久代)中,当方法区满的时候,会引起Full GC,所以当你大量使用动态代理时,可以将持久代设置大一些,减少Full GC次数。

  1. Cglib 动态字节码生成

原理:在运行期,目标类加载后,动态构建字节码文件生成目标类的子类,将切面逻辑加入到子类中
优缺点:没有接口也可以织入,扩展类的实例方法为final时,无法进行织入。

  1. 自定义类加载器

原理:在运行期,目标加载前,将切面逻辑加到目标字节码里,Javassist。
优缺点:可以对绝大部分类进行织入,代码中若使用了其它类加载器,则这些类将不会被织入。

  1. 字节码转换

原理:在运行期,所有类加载器加载字节码前进行拦截。
优缺点:可以对所有类进行织入

Spring AOP使用

Aop(Aspect Oriented Programming),面向切面编程,这是对面向对象思想的一种补充。
面向切面编程,就是在程序运行时,不改变程序源码的情况下,动态的增强方法的功能,常见的使用场景非常多:

  1. 日志
  2. 事务
  3. 数据库操作

AOP(Aspect-Oriented Programming, 面向切面编程):

是一种新的方法论, 是对传统 OOP(Object-Oriented Programming, 面向对象编程) 的补充哦.
AOP 的主要编程对象是切面(aspect),而切面模块化横切关注点.
在应用 AOP 编程时, 仍然需要定义公共功能, 但可以明确的定义这个功能在哪里, 以什么方式应用, 并且不必修改受影响的类. 这样一来横切关注点就被模块化到特殊的对象(切面)里.

AOP 的好处:

每个事物逻辑位于一个位置, 代码不分散, 便于维护和升级
业务模块更简洁, 只包含核心业务代码.

AspectJ:Java 社区里最完整最流行的 AOP 框架.
在 Spring2.0 以上版本中, 可以使用基于 AspectJ 注解或基于 XML 配置的 AOP。 AOP跟AspectJ区别参考

Spring默认采取的动态代理机制实现AOP,当动态代理不可用时(代理类无接口)会使用CGlib机制。但Spring的AOP有一定的缺点,第一个只能对方法进行切入,不能对接口,字段,静态代码块进行切入(切入接口的某个方法,则该接口下所有实现类的该方法将被切入)。第二个同类中的互相调用方法将不会使用代理类。因为要使用代理类必须从Spring容器中获取Bean。第三个性能不是最好的,使用自定义类加载器,性能要优于动态代理和CGlib。

工程中业务代码前后,无一例外,都有很多模板化的代码,而解决模板化代码,消除臃肿就是 Aop 的强项。Spring概述 说过Spring的一个核心就是引入了切面概念,先看下如何用的。

  1. 引入aspects 包
     <dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>5.0.6.RELEASE</version></dependency>
  1. 真正的实体化方法
// 计算类
public class Calculator {//业务逻辑方法public int div(int i, int j)  {System.out.println("--------");return i/j;}
}
//日志切面类
@Aspect
public class LogAspects {@Pointcut("execution(public int com.enjoy.cap10.aop.Calculator.*(..))")public void pointCut() {}//@before代表在目标方法执行前切入, 并指定在哪个方法前切入  获得方法名, 方法参数列表@Before("pointCut()")public void logStart(JoinPoint joinPoint) {System.out.println(joinPoint.getSignature().getName() + "除法运行.Before 参数列表是:{" + Arrays.asList(joinPoint.getArgs()) + "}");}@After("pointCut()")public void logEnd(JoinPoint joinPoint) {System.out.println(joinPoint.getSignature().getName() + "除法结束.After.....");}// 结果获得    @AfterReturning(value = "pointCut()", returning = "result")public void logReturn(Object result) {System.out.println("除法正常返回..AfterReturning..运行结果是:{" + result + "}");}@AfterThrowing(value = "pointCut()", throwing = "exception")public void logException(Exception exception) {System.out.println("运行异常. AfterThrowing...异常信息是:{" + exception + "}");}@Around("pointCut()")public Object Around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{System.out.println("@Arount:执行目标方法之前...");Object obj = proceedingJoinPoint.proceed(); // 相当于开始调div地System.out.println("@Arount:执行目标方法之后...");return obj;}
}
  1. 系统调用
/** 日志切面类的方法需要动态感知到div()方法运行, *  通知方法:*     前置通知:logStart(); 在我们执行div()除法之前运行(@Before)*     后置通知:logEnd();在我们目标方法div运行结束之后 ,不管有没有异常(@After)*     返回通知:logReturn();在我们的目标方法div正常返回值后运行(@AfterReturning)*     异常通知:logException();在我们的目标方法div出现异常后运行(@AfterThrowing)*     环绕通知:动态代理, 需要手动执行joinPoint.procced()(其实就是执行我们的目标方法div,), 执行之前div()相当于前置通知, 执行之后就相当于我们后置通知(@Around)*/
@Configuration
@EnableAspectJAutoProxy
public class AspectTest {@Beanpublic Calculator calculator(){return new Calculator();}@Bean  // 切记要将切面注册到容器中public LogAspects logAspects(){return new LogAspects();}public static void main(String[] args) {AnnotationConfigApplicationContext app = new AnnotationConfigApplicationContext(AspectTest.class);Calculator c = app.getBean(Calculator.class);int result = c.div(4, 3);System.out.println(result);app.close();}
}


小结: AOP看起来很麻烦, 只要3步就可以了:

  1. 将业务逻辑组件和切面类都加入到容器中, 告诉spring哪个是切面类(@Aspect)
  2. 在切面类上的每个通知方法上标注通知注解, 告诉Spring何时运行(写好切入点表达式,参照官方文档)
  3. 开启基于注解的AOP模式 @EableXXXX

AOP源码跟踪

目的:看AOP给容器中注册了什么组件,这个组件什么时候工作,这个组件的功能是什么?
入口@EnableAspectJAutoProxy,核心从这个入手,AOP整个功能要启作用,就是靠这个,加入它才有AOP

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class) // 此乃重点 引入此类。
public @interface EnableAspectJAutoProxy {//默认false,采用JDK动态代理织入增强(实现接口的方式);// 如果设为true,则采用CGLIB动态代理织入增强boolean proxyTargetClass() default false;//通过aop框架暴露该代理对象,aopContext能够访问boolean exposeProxy() default false;
}

AspectJAutoProxyRegistrar, 并实现了ImportBeanDefinitionRegistrar接口,ImportBeanDefinitionRegistrar能给容器中自定义注册组件。

class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);// 上面这个是重点....}
}
 @Nullablepublic static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry) {return registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry, null);}@Nullablepublic static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry,@Nullable Object source) {return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);// 进入}
 @Nullable  // cls = AnnotationAwareAspectJAutoProxyCreator.classprivate static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, BeanDefinitionRegistry registry,@Nullable Object source) {Assert.notNull(registry, "BeanDefinitionRegistry must not be null");if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);if (!cls.getName().equals(apcDefinition.getBeanClassName())) {int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());int requiredPriority = findPriorityForClass(cls);if (currentPriority < requiredPriority) {apcDefinition.setBeanClassName(cls.getName());}}return null;}RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);beanDefinition.setSource(source);beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition); // 重点注册
// name = internalAutoProxyCreator class 就是 AnnotationAwareAspectJAutoProxyCreatorreturn beanDefinition;}

结论:因此我们要重点研究AnnotationAwareAspectJAutoProxyCreator组件(ASPECT自动代理创建器), 研究这个透了, 整个原理也就明白了, 所有的原理就是看容 器注册了什么组件, 这个组件什么时候工作, 及工作时候的功能是什么? 只要把这几个研究清楚了,原理就都清楚了。

那我们来分析做为beanPostProcessor后置处理器做了哪些工作, 做为BeanFactoryAware又做了哪些工作。

现有个这样的思想声明跟创建还有注入,AOP的Bean相比与普通的Bean 区别无非就是用RootBeanDefinitionAUTO_PROXY_CREATOR_BEAN_NAME = AnnotationAwareAspectJAutoProxyCreator声明下,然后在我们创建Bean的时候相比于普通Bean的创建,它的创建时间比较早。
一句话就是将切面核心类声明然后注入到容器中,并且要早于业务普通Bean

创建和注册AnnotationAwareAspectJAutoProxyCreator的流程

用人话简单说下思路无非就是先把我们需要的Bean 声明下,然后再进行创建跟注册。下面是AOP核心类的过程。

  1. register()传入配置类,准备创建ioc容器
  2. 注册配置类,调用refresh()刷新创建容器;
  3. registerBeanPostProcessors(beanFactory);注册bean的后置处理器来方便拦截bean的创建(主要是分析创建AnnotationAwareAspectJAutoProxyCreator);
  1. 先获取ioc容器已经定义了的需要创建对象的所有BeanPostProcessor
  2. 给容器中加别的BeanPostProcessor
  3. 优先注册实现了PriorityOrdered接口的BeanPostProcessor;
  4. 再给容器中注册实现了Ordered接口的BeanPostProcessor;
  5. 注册没实现优先级接口的BeanPostProcessor;
  6. 注册BeanPostProcessor,实际上就是创建BeanPostProcessor对象,保存在容器中;创建internalAutoProxyCreator的BeanPostProcessor【其实就是AnnotationAwareAspectJAutoProxyCreator】
  1. 创建Bean的实例
  2. populateBean;给bean的各种属性赋值
  3. initializeBean:初始化bean;
  1. invokeAwareMethods():处理Aware接口的方法回调
  2. iapplyBeanPostProcessorsBeforeInitialization():应用后置处理器的postProcessBeforeInitialization()
  3. invokeInitMethods();执行自定义的初始化方法
  4. applyBeanPostProcessorsAfterInitialization();执行后置处理器的postProcessAfterInitialization()
  1. BeanPostProcessor(AnnotationAwareAspectJAutoProxyCreator)创建成功:aspectJAdvisorsBuilder
  1. 把BeanPostProcessor注册到BeanFactory中:beanFactory.addBeanPostProcessor(postProcessor);

Calculator的装载跟使用

有点绕,懒的写了,基本的思想就是进行业务类代码的单实例装载,期间会有各种判断,最终会对该类中的一些方法进行增强(AnnotationAwareAspectJAutoProxyCreator就是干这个事的,会选择性的判断我们的业务类是否需要增强),然后最终搞成了一个目标类,此时我们再Calculator c这样再通过c调用方法的时候就不是简单的方法调用了。大致就是下面两个步骤:

  1. 获取拦截链–MethodInterceptor
  2. 链式调用通知方法

AOP核心源码流程图

网上找的,跟着源码一步步看还挺通俗易懂的(AOP源码流程图),PS想获取可以关注我公众号回复AOP获取高清图。

参考

通俗说AOP
AOP简单讲解

【Spring】3.助你跟面试官侃一个小时的AOP相关推荐

  1. 跟面试官侃半小时MySQL事务隔离性,从基本概念深入到实现

    来源 | 阿丸笔记 提到MySQL的事务,我相信对MySQL有了解的同学都能聊上几句,无论是面试求职,还是日常开发,MySQL的事务都跟我们息息相关. 而事务的ACID(即原子性Atomicity.一 ...

  2. 事物与持久化_跟面试官侃半小时MySQL事务,说完原子性、一致性、持久性的实现...

    提到MySQL的事物,我相信对MySQL有了解的同学都能聊上几句,无论是面试求职,还是日常开发,MySQL的事务都跟我们息息相关. 而事务的ACID(即原子性Atomicity.一致性Consiste ...

  3. Re:从零开始的DS生活 轻松和面试官扯一个小时栈

     前言

  4. 跟面试官侃了半小时 MySQL 事务,把原子性、一致性、持久性的实现都讲完了

    来源 | 阿丸笔记 封图| CSDN下载于视觉中国 提到MySQL的事务,我相信对MySQL有了解的同学都能聊上几句,无论是面试求职,还是日常开发,MySQL的事务都跟我们息息相关. 而事务的ACID ...

  5. Java面试集锦:面试官只问一个问题,30几人集体懵圈

    Java面试集锦:面试官只问一个问题,30几人集体懵圈 有的时候面试官的问题真的层出不穷,今天就又遇到了一个奇葩的问题(你是面试官你说了算).面试时候,直接给拿出了一个特别"复杂" ...

  6. 看了这个文章你也可以和面试官侃半个小时hashMap了

    HashMap应该算是Java后端工程师面试的必问题,因为其中的知识点太多,很适合用来考察面试者的Java基础. 面试官: 你先自我介绍一下吧! 我: 我是安琪拉,草丛三婊之一,最强中单(钟馗不服)! ...

  7. 动画:如何给面试官写一个满意的冒泡排序

    作者 | 小鹿 来源 | 小鹿动画学编程 写在前边 对于冒泡排序,很多小伙伴已经可以说很熟悉了,顺手就可以写出来,但对于一个初学者来说,小鹿想通过这篇文章,让你一次性就理解冒泡排序以及冒泡排序的优化, ...

  8. 面试官:一个能一网打尽的技术问题

    往期精选 ●  架构师高并发高性能分布式教程(4000G) ●  39阶段精品云计算大数据实战视频教程 ●  互联网技术干货视频教程大全[菜单为准] ●  2017年8月最新Intellij IDEA ...

  9. spring中的设计模式_面试官:来给我说一下 Spring 中使用了哪些设计模式?

    文章来源:itxxz.com/a/javashili/tuozhan/2014/0601/7.html 导读:设计模式作为工作学习中的枕边书,却时常处于勤说不用的尴尬境地,也不是我们时常忘记,只是一直 ...

最新文章

  1. 如何在CrossOver里应用Windows容器的存档(备份)与恢复?
  2. JS判断是否是移动设备进行http链接重定向
  3. 善于 调用Windows API
  4. react学习(8)----数组方法fliter简介
  5. Linux 内核编码风格【转】
  6. 灵活运用 SQL SERVER FOR XML PATH
  7. C++:不同数据类型作为参数传递和作为返回值的例子
  8. iTextSharp显示中文
  9. Windows 8 简体中文 官方正式版 原版镜像下载
  10. Python杀死了Excel
  11. Apache Calcite教程-SQL解析-Calcite SQL解析
  12. “加密上海·喜玛拉雅Web3.0数字艺术大展”落幕,AIGC和数字艺术衍生品是最大赢家?...
  13. 浅谈股价预测模型:捉摸不定,最为致命
  14. python爬虫实例电商_Python实现爬取并分析电商评论
  15. 抗旋转matlab算法,抗旋转核心训练:2个动作推荐
  16. 现代控制理论(3)——线性控制系统的能控性和能观性
  17. 网络安全实验室—基础关
  18. 计算机组成原理:比较SRAM和DRAM存储器
  19. 分享我自己的导航网站bituplink导航 (附GitHub开源代码项目OneHtmlNav)
  20. 2022年印度电商市场现状与发展前景

热门文章

  1. k1658停运_武汉局集团公司近期临时停运列车的公告
  2. Oracle提高命中率及优化
  3. 命中率 计算机组成原理,计算机组成原理-求命中率.pptx
  4. 解决IDEA 前端返回值乱码问题
  5. 计算机英语情景对话二人组,英语小对话二人组日常情景对话
  6. 怒刷python作业(西北工业大学cpSkill平台)
  7. html不同板块点击切换,板块快速切换的操作策略
  8. 动易安全开发手册 完整版
  9. MATLAB箭头绘制 arrow3 函数与 quiver3 函数的实用教程
  10. Java8里不得不说的那些常用日期处理,码起来~