您好!
欢迎来到京东云开发者社区
登录
首页
博文
课程
大赛
工具
用户中心
开源
首页
博文
课程
大赛
工具
开源
更多
用户中心
开发者社区
>
博文
>
AspectJ浅析系列(一)- 初识AOP
分享
打开微信扫码分享
点击前往QQ分享
点击前往微博分享
点击复制链接
AspectJ浅析系列(一)- 初识AOP
自猿其说Tech
2021-08-04
IP归属:未知
99920浏览
计算机编程
### 1 前言 本文首先简单介绍了OOP和AOP的概念,继而引出了AOP的一种完全解决方案---AspectJ,在介绍了其两种分类和三种时机后,用一个浅显的Hello World程序来讲解基础概念。最后总结本文的全部内容,并且在文章末尾罗列了参考书籍和部分信息来源,以示感谢。 ### 2 什么是AOP 在说AOP前,有必要先说什么是OOP。OOP就是面向对象编程(Object Oriented Programming)的缩写,我们日常的开发离不开的东西,面向对象可以帮助我们将相同的业务功能集成在一起,极大的帮助我们快速开发一套系统。 但是总会遇到一些业务,对原有的系统框架造成影响。比如日志的打印,在关键的地方就会打印一下订单的信息。这样子便是破坏了其五大原则之一的单一功能原则(Single Responsibility Principle)简称SRP。当一个模块来同时处理多个关注点时,会导致代码混乱(Code tangling)。当单个功能在多个模块中实现时,会导致代码分散(Code scattering)。最明显的就是在好多地方都用到了日志打印。 面向对象可以帮助我们将相同的业务功能集成在一起,但是当需要使用权限验证或者日志记录的时候,每个模块内都要做大量繁复的工作,这个时候如果同时使用AOP(aspect-oriented programming)就有很大的帮助了!AOP是和OOP结合在一起使用的,OOP继承,多态等从上到下的纵向树形结构,再加上水平的AOP横切,构成网,所以称为织网是很形象的。 #### 2.1 一些概念 - 关注点(Concern)。对软件工程有意义的小的、可管理的、**可描述的软件组成部分**,比如订单业务功能,Jimdb的数据存取功能,API调用,注解定义等。 - 横切关注点(AspectJ in Action)。这种关注点会遍布在整个软件系统内部,与多个模块之间发生关系,类似权限验证,日志,资源池等。 **一种切实的AOP语言需要关注两点:** 1. 找到横切关注点 1. 在该处进行操作,并将结果信息转化成可运行的代码。 下面两图取自AspectJ in Action第二版,用编译器的方式去抓核心关注点和横切关注点,从而形成整个体系。 ![](//img1.jcloudcs.com/developer.jdcloud.com/cf32e34b-6880-46a9-9b55-a196a858d01020210804140416.png) Spring AOP 和AspectJ都是AOP的解决方案,也都是AOP系统。 如何判断一个系统是AOP系统?只要满足以下关键就可以: - 连接点(Joint point):暴露系统运行的点,可以是方法,对象,异常,即任何能作为代码注入目标的特定的点和入口。(Identifiable points in the execution of the system) - 切入点(Point cut):是用类似SQL的查询方式去指定的一个或者一批连接点,起别名称为切入点。应该还可以用其他的切入点来简化查询,且能够获得连接点的上下文。(A construct for selecting join points)告诉代码注入工具,在何处注入一段特定代码的表达式。 - 通知(Advice):在找到切入点之后,要操作的方法。(After a pointcut selects join points, you must augment those join points with additional or alternative behavior.) - 动态横切(dynamic crosscutting):所有影响到系统运行的都是动态横切(dynamic crosscutting alters the program behavior)。例如给方法添加运行时间。 - 静态横切(static crosscutting):所有修改了程序结构的都属于静态横切(static crosscutting alters theprograms structure.)。例如增添属性和继承关系。 - 切面(Aspect):一个集成了切入点,通知和静态横切的类或者包(a place to express embeds crosscutting logic.) - 织入(Weaving): 注入代码(Advice)到目标位置(Point cut)的过程。 一切关系如下图所示,这是最全的,但不是最常用的: ![](//img1.jcloudcs.com/developer.jdcloud.com/386519e0-2876-496d-9200-96a6a83ecc1320210804140511.png) 到这里为止,AOP作为一种编程思想已经说完了。接下来就是我们这个系列的主角了---AspectJ出场。 ### 3 AspectJ的出现 就如同OOP中有C++和Java等,AOP也有许多种实现,其中的一种完全解决方案就是AspectJ。 AspectJ是一种面向切面的扩展Java语言(AspectJ is an aspectoriented extension to the Java programming language.)。AspectJ有自己的语法规则,能够兼容Java的所有,所构造的是*.aj的文件。AspectJ用特有的编译器ajc编译出class文件来。包含定义和实现两个部分。 AspectJ的原理是用asm做字节码替换达到AOP的目的。 #### 3.1 动态横切 AspectJ的动态横切,提供了两种切面织入方式,三种织入时机。 第一种织入方式,本质是通过特殊编译器,将Aspectj语言编写的切面文件**.aj编译成为class文件,在class文件生成后,就已经织入好了。实现的时候可以通过Maven插件来完成。对应于两个织入时机: 1. compile-time:编译期织入,将切面类和服务类放在一起用ajc编译,直接编译出包含织入代码的 .class 文件。适合于切面和服务在同一个项目中的情况。 1. post-compile:编译后织入,切面类或者服务类已经被打成jar包了,这时候也可以用ajc命令将jar再织入一次。增强已经编译出来的类,适合于切面和服务不在同一个项目中的的情况。 第二种织入方式,是在JVM 期织入,方式做字节码的替换完成织入。对应的织入时机是Load Time Weaving,简称LTW。实现方式中较为常用的是将aspectjweaver.jar作为javaagent参数来启动JVM。 #### 3.2 静态横切 AspectJ的静态横切,里面则有两种应用。 1. weave-time declaration:简称为WTD,主要是修改类编译阶段的行为,有三种:添加警告;添加错误;忽略必检异常。 1. inter-type declaration:简称为ITD,主要是修改类的结构,有三种:给类添加字段,方法;修改类的继承或实现;给类或者类中的方法动态添加注解。 在实际的研发过程中,使用动态横切更多一些,真实的例子就是将日志打印和UMP抽取成为自定义注解,从而可以让业务完全忽略掉他们的影响,专心在业务逻辑的编程上面。这个例子会在第三篇进行介绍,现在先让我们来输出一条Hello World吧! ### 4 HelloWorld 接下来我们先实现一个动态横切的例子。 #### 4.1 原方法 先新建一个普通的maven项目,编写服务和主程序入口: ![](//img1.jcloudcs.com/developer.jdcloud.com/469693e2-e8ec-43ee-adef-fe4d0e8f314320210804140626.png) 服务: ```java package cn.jdl.ka.service; public class KeenService { public void printHelloWorld(){ System.out.println("Hello World!"); } } ``` 主程序入口: ```java package cn.jdl.ka; public class KeenMain { public static void main(String[] args) { KeenService keenService = new KeenService(); keenService.printHelloWorld(); } } ``` 直接运行得到结果 ![](//img1.jcloudcs.com/developer.jdcloud.com/4c52e67b-2e76-4a07-a13a-228c25066e8820210804140840.png) #### 4.2 织入方法 现在来了一个新需求,想要在不修改原先服务文件的前提下,在调用printHelloWorld方法后,同时输出中文和英文的内容,这个时候就可以用AspectJ去实现了。 ![](//img1.jcloudcs.com/developer.jdcloud.com/c8f7fdf9-9b80-4eaa-aca6-6cf73f72edb520210804140902.png) 编写切面KeenAspect.java实现在该方法运行前,在控制台打印“你好,世界!”: ```java package cn.jdl.ka.aspect; @Aspect public class KeenAspect { //切入点 @Pointcut("execution(public void cn.jdl.ka.service.KeenService.printHelloWorld())") public void pointCut(){} @Before("pointCut()") public void beforePrintHelloWorld(JoinPoint joinPoint) {//通知内容 System.out.println("你好,世界!"); } } ``` 为什么这样子写?各种注解的存在和语法介绍怎么没有?且容我卖个关子,先将基础的Demo项目跑起来。 ##### 4.2.1 编织期织入 加上依赖和需要的插件: pom.xml: ```xml <dependencies> <!--添加依赖--> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.8.13</version> </dependency> </dependencies> <build> <plugins> <!--aspectj编译--> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>aspectj-maven-plugin</artifactId> <version>1.11</version> <configuration> <!--对应编译器版本--> <complianceLevel>1.8</complianceLevel> <!--对应java1.8--> <source>1.8</source> <!--对应class文件版本--> <target>1.8</target> <!--展示信息--> <showWeaveInfo>true</showWeaveInfo> <!--忽略警告--> <Xlint>ignore</Xlint> <!--设置通用编码--> <encoding>UTF-8</encoding> </configuration> <executions> <execution> <goals> <!--编织主类--> <goal>compile</goal> <!--编织测试类--> <goal>test-compile</goal> </goals> </execution> </executions> </plugin> <!--运行--> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>1.4.0</version> <executions> <execution> <goals> <goal>java</goal> </goals> </execution> </executions> <configuration> <!-- executable指的是要执行什么样的命令 --> <executable>java</executable> <arguments> <!-- 这是通用编码--> <argument>-Dfile.encoding=utf-8</argument> <!-- 这是classpath属性,其值就是下面的<classpath/> --> <argument>-classpath</argument> <!--工程的classpath不需要手动指定,将由exec自动计算得出 --> <classpath/> <!-- 程序入口,主类名称 --> <argument>cn.jdl.ka.KeenMain</argument> </arguments> </configuration> </plugin> </plugins> </build> ``` 之后用这个方式去运行: ![](//img1.jcloudcs.com/developer.jdcloud.com/6aa40a60-7635-481f-97ad-66c6eea6d4b020210804141021.png) 第一步编译,两个选项都可以。编译成功后第二步运行,即可得到结果: ![](//img1.jcloudcs.com/developer.jdcloud.com/c2afe273-2cd3-43cd-88b2-8f1f8a30809820210804141040.png) 到了这里,就算是成功地在编译期织入通知,完成了Hello World! 有小伙伴就想问了,我要是用application的方式运行,方便更好的Debug要怎么做? 第一步编译是不能够省略的,因为如果默认编译的话是用的javac做的。 第二步的就可以不用exec插件,在选项中选择运行前不编译(否则会自动用javac覆盖的),运行后可以在命令行那里看到结果。 ![](//img1.jcloudcs.com/developer.jdcloud.com/93398019-e8f5-4a4f-b988-8eca5a38092220210804141104.png) ##### 4.2.2 JVM加载期 与上面的有一点点的不同。因为是在JVM期再织入的,所以需要用一个方式指定切面所在的位置: ![](//img1.jcloudcs.com/developer.jdcloud.com/130269d8-c8ed-479c-b7fd-7d6da6f0b0a020210804141124.png) resource/META-INF/aop.xml文件: ```xml <?xml version="1.0" encoding="UTF-8"?> <aspectj> <aspects> <!-- 向编织器声明一个切面,只支持java文件,不支持aj文件 ,除非切换编译器--> <aspect name="cn.jdl.ka.aspect.KeenAspect"/> </aspects> </aspectj> ``` pom.xml文件: ```xml <dependencies> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.8.13</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.13</version> </dependency> </dependencies> ``` 首先先自动下载所需要的依赖,然后如下配置exec插件来实现: ```xml <build> <plugins> <!--运行--> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>1.4.0</version> <executions> <execution> <goals> <goal>java</goal> </goals> </execution> </executions> <configuration> <!-- executable指的是要执行什么样的命令 --> <executable>java</executable> <arguments> <!-- 这是通用编码--> <argument>-Dfile.encoding=utf-8</argument> <!--添加javaagent属性--> <argument>-javaagent:"D:\MavenRepository\org\aspectj\aspectjweaver\1.8.13\aspectjweaver-1.8.13.jar"</argument> <!-- 这是classpath属性,其值就是下面的<classpath/> --> <argument>-classpath</argument> <!-- 这是exec插件最有价值的地方,关于工程的classpath并不需要手动指定,它将由exec自动计算得出 --> <classpath/> <!-- 程序入口,主类名称 --> <argument>cn.jdl.ka.KeenMain</argument> </arguments> </configuration> </plugin> </plugins> </build> ``` 如此之后,使用maven启动: ![](//img1.jcloudcs.com/developer.jdcloud.com/6dee66dd-cb24-41a3-bdfd-466a8648a7e820210804141237.png) 填入name属性做名字,选择当前模块的工作目录,在命令行中填入: ```xml clean compile exec:exec ``` 即可一键运行,得到同样的结果! 又有小伙伴就想问了,我不想配置大段的maven插件,我就是要用application的方式运行要怎么做? 那样子的话,pom.xml文件里面则只需要保留依赖: ```xml <dependencies> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.8.13</version> </dependency> </dependencies> ``` 在启动选项里面选择添加VM参数: ![](//img1.jcloudcs.com/developer.jdcloud.com/7a7deb98-d101-476b-aaf7-cd207b4f1cea20210804141325.png) 再填入如下语句即可运行,其中的前缀是本地maven仓库: ```xml -javaagent:"D:\MavenRepository\org\aspectj\aspectjweaver\1.8.13\aspectjweaver-1.8.13.jar" ``` 注意!在这里面的jar包是从maven仓库里面导入进来的,所以需要先引一次依赖,再添加该语句。 ### 5 小结 本文简单介绍了AOP的基础概念,引出了AspectJ的存在,并且演示了一个动态横切的例子,分别在编译期以及在JVM期成功地织入了前置通知。为了省去了启动命令行的步骤,在pom文件的插件配置里加入了编译插件和运行插件省去了ajc的命令,更为明了。 这一次选择的横切切入点是方法调用的时候,语法格式是直接定义到了该方法。但是实际业务的时候,是肯定要用到通配符的。而且在找到了横切切入点之后,就要选择使用的通知方式了。通知包括了五大类,前置通知,后置通知,返回通知,异常通知和全能的环绕通知方式,在某类通知里甚至还可以修改传入的参数的。 读到这里,你可能还有疑问,之后在业务方面如何应用?在实际操作和编码中会有哪些坑需要注意?这些在之后的系列都会一一解答的。 敬请期待AspectJ浅析系列第二篇————切入点和通知。 #### 感谢 AspectJ in Action第二版 [AOP 面向切面编程] https://www.95id.com/aop-Introduction.html [aspectj-maven-plugin 插件使用] https://blog.csdn.net/mamamalululu00000000/article/details/111264804 [Exec Maven Plugin全面解析和使用示例] https://blog.csdn.net/bluishglc/article/details/7622286 [Aspectj LTW 实践] https://www.95id.com/aspectj-ltw-introduction.html ------------ ###### 自猿其说Tech-JDL京东物流技术发展部 ###### 作者:销售发展技术部 魏继扩
原创文章,需联系作者,授权转载
上一篇:优雅的使用线程池以及源码剖析
下一篇:点亮B端产品技能树——业务调研
相关文章
Taro小程序跨端开发入门实战
Flutter For Web实践
配运基础数据缓存瘦身实践
自猿其说Tech
文章数
426
阅读量
2149964
作者其他文章
01
深入JDK中的Optional
本文将从Optional所解决的问题开始,逐层解剖,由浅入深,文中会出现Optioanl方法之间的对比,实践,误用情况分析,优缺点等。与大家一起,对这项Java8中的新特性,进行理解和深入。
01
Taro小程序跨端开发入门实战
为了让小程序开发更简单,更高效,我们采用 Taro 作为首选框架,我们将使用 Taro 的实践经验整理了出来,主要内容围绕着什么是 Taro,为什么用 Taro,以及 Taro 如何使用(正确使用的姿势),还有 Taro 背后的一些设计思想来进行展开,让大家能够对 Taro 有个完整的认识。
01
Flutter For Web实践
Flutter For Web 已经发布一年多时间,它的发布意味着我们可以真正地使用一套代码、一套资源部署整个大前端系统(包括:iOS、Android、Web)。渠道研发组经过一段时间的探索,使用Flutter For Web技术开发了移动端可视化编程平台—Flutter乐高,在这里希望和大家分享下使用Flutter For Web实践过程和踩坑实践
01
配运基础数据缓存瘦身实践
在基础数据的常规能力当中,数据的存取是最基础也是最重要的能力,为了整体提高数据的读取能力,缓存技术在基础数据的场景中得到了广泛的使用,下面会重点展示一下配运组近期针对数据缓存做的瘦身实践。
自猿其说Tech
文章数
426
阅读量
2149964
作者其他文章
01
深入JDK中的Optional
01
Taro小程序跨端开发入门实战
01
Flutter For Web实践
01
配运基础数据缓存瘦身实践
添加企业微信
获取1V1专业服务
扫码关注
京东云开发者公众号