什么是 AOP

AOP(Aspect-OrientedProgramming,面向方面编程),可以说是 OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP 引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP 则显得无能为力。也就是说,OOP 允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在 OOP 设计中,它导致了大量代码的重复,而不利于各个模块的重用。

AOP 实现的关键在于 AOP 框架自动创建的 AOP 代理,AOP 代理主要分为静态代理和动态代理,静态代理的代表为 AspectJ;而动态代理则以 Spring AOP 为代表。静态代理是编译期实现,动态代理是运行期实现,可想而知前者拥有更好的性能。

静态代理是编译阶段生成 AOP 代理类,也就是说生成的字节码就织入了增强后的 AOP 对象;动态代理则不会修改字节码,而是在内存中临时生成一个 AOP 对象,这个 AOP 对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。

Spring AOP 中的动态代理主要有两种方式,JDK 动态代理和 CGLIB 动态代理。JDK 动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK 动态代理的核心是 InvocationHandler 接口和 Proxy 类。

如果目标类没有实现接口,那么 Spring AOP 会选择使用 CGLIB 来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意,CGLIB 是通过继承的方式做的动态代理,因此如果某个类被标记为 final,那么它是无法使用 CGLIB 做动态代理的,诸如 private 的方法也是不可以作为切面的。

我们分别通过实例来研究 AOP 的具体实现。

直接使用 Spring AOP

首先需要引入相关依赖,我这里是用了 SpringBoot 的相关 starter

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>

然后定义需要切入的接口和实现。为了简单起见,定义一个接口Speakable和一个具体的实现类PersonSpring,只有两个方法sayHi()sayBye()

public interface Speakable {void sayHi();void sayBye();
}
@Service
public class PersonSpring implements Speakable {@Overridepublic void sayHi() {try {Thread.currentThread().sleep(30);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Hi!!");}@Overridepublic void sayBye() {try {Thread.currentThread().sleep(30);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Bye!!");}}

现在我们需要实现一个功能,记录sayHi()sayBye()的执行时间。
我们定义了一个MethodMonitor类用来记录 Method 执行时间

public class MethodMonitor {private long start;private String methodName;public MethodMonitor(String methodName) {this.methodName = methodName;this.start = System.currentTimeMillis();System.out.println(this.methodName + "monitor begin...");}public void log() {long elapsedTime = System.currentTimeMillis() - start;System.out.println("Method:" + this.methodName + ", elapsedTime:" + elapsedTime + "millis");System.out.println(this.methodName + "monitor end.");}}

光有这个类还是不够的,希望有个静态方法用起来更顺手,像这样

MonitorSession.start(methodName);
doWork();
MonitorSession.end();

说干就干,定义一个MonitorSession

public class MonitorSession {private static ThreadLocal<MethodMonitor> threadLocal = new ThreadLocal<>();public static void start(String methodName) {threadLocal.set(new MethodMonitor(methodName));}public static void end() {threadLocal.get().log();}}

准备工作都做完了, 接下来只需要我们做好切面的编码

@Aspect
@Component
public class MonitorAdvice {@Pointcut("execution (* com.windmt.springaop.service.Speakable.*(..))")public void pointcut() {}@Around("pointcut()")public void around(ProceedingJoinPoint pjp) throws Throwable {MonitorSession.start(pjp.getSignature().getName());pjp.proceed();MonitorSession.end();}}

如何使用?写一个启动函数吧。

@SpringBootApplication
public class SpringAopApplication {@Autowiredprivate Speakable personSpring;public static void main(String[] args) {SpringApplication.run(SpringAopApplication.class, args);}@Beanpublic CommandLineRunner commandLineRunner(ApplicationContext ctx) {return args -> {System.out.println("********** spring aop **********");personSpring.sayHi();personSpring.sayBye();System.exit(0);};}}

运行后输出:

********** spring aop **********
sayHi monitor begin...
Hi!!
Method:sayHi, elapsedTime:48 millis
sayHi monitor end.
sayBye monitor begin...
Bye!!
Method:sayBye, elapsedTime:34 millis
sayBye monitor end.

JDK 动态代理

刚刚的例子其实内部实现机制就是 JDK 动态代理,因为PersonSpring实现了一个接口。
为了不和第一个例子冲突,我们再定义一个PersonIndie来实现Speakable,实现和之前的完全一样,只是注意这个实现是不带 Spring Annotation 的,所以他不会被 Spring 托管。

public class PersonIndie implements Speakable {@Overridepublic void sayHi() {try {Thread.currentThread().sleep(30);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Hi!!");}@Overridepublic void sayBye() {try {Thread.currentThread().sleep(30);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Bye!!");}}

重头戏来了,我们需要利用InvocationHandler实现一个代理,让它去包含Person这个对象。那么在运行时实际上执行的是这个代理的方法,然后代理再去执行真正的方法。所以我们得以在执行真正方法的前后做一些手脚。JDK 动态代理是利用反射实现,直接看代码。

public class DynamicProxy implements InvocationHandler {private Object target;public DynamicProxy(Object obj) {this.target = obj;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {MonitorSession.start(method.getName());Object obj = method.invoke(this.target, args);MonitorSession.end();return obj;}public <T> T getProxy() {return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);}}

通过getProxy可以得到这个代理对象,invoke就是具体的执行方法,可以看到我们在执行每个真正的方法前后都加了 Monitor。

再来一个工厂类来获取 Person 代理对象

public class PersonProxyFactory {public static Speakable newJdkProxy() {DynamicProxy proxy = new DynamicProxy(new PersonIndie());return proxy.getProxy();}}

具体使用

@SpringBootApplication
public class SpringAopApplication {public static void main(String[] args) {SpringApplication.run(SpringAopApplication.class, args);}@Beanpublic CommandLineRunner commandLineRunner(ApplicationContext ctx) {return args -> {System.out.println("********** spring jdk proxy **********");Speakable person = PersonProxyFactory.newJdkProxy();person.sayHi();person.sayBye();System.exit(0);};}}

运行并输出:

********** spring jdk proxy **********
sayHi monitor begin...
Hi!!
Method:sayHi, elapsedTime:35 millis
sayHi monitor end.
sayBye monitor begin...
Bye!!
Method:sayBye, elapsedTime:32 millis
sayBye monitor end.

CGLib 动态代理

我们再新建一个Person来,这次不实现任何接口。

public class Person {public void sayHi() {try {Thread.currentThread().sleep(30);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Hi!!");}public void sayBye() {try {Thread.currentThread().sleep(30);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Bye!!");}}

如果 Spring 识别到所代理的类没有实现 Interface,那么就会使用 CGLib 来创建动态代理,原理实际上成为所代理类的子类。

public class CGLibProxy implements MethodInterceptor {private static volatile CGLibProxy instance;private CGLibProxy() {}public static CGLibProxy getInstance() {if (instance == null) {synchronized (CGLibProxy.class) {if (instance == null) {instance = new CGLibProxy();}}}return instance;}@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {MonitorSession.start(method.getName());Object obj = methodProxy.invokeSuper(o, objects);MonitorSession.end();return obj;}private Enhancer enhancer = new Enhancer();public <T> T getProxy(Class<T> clazz) {enhancer.setSuperclass(clazz);enhancer.setCallback(this);return (T) enhancer.create();}}

类似的通过getProxy可以得到这个代理对象,intercep就是具体的执行方法,可以看到我们在执行每个真正的方法前后都加了 Monitor。
在工厂类中增加获得 Person 代理类的方法

public static Person newCGLibProxy() {return CGLibProxy.getInstance().getProxy(Person.class);
}

具体使用

Person person = PersonProxyFactory.newCGLibProxy();
person.sayHi();
person.sayBye();

输出结果

********** CGLib proxy **********
sayHi monitor begin...
Hi!!
Method:sayHi, elapsedTime:38 millis
sayHi monitor end.
sayBye monitor begin...
Bye!!
Method:sayBye, elapsedTime:35 millis
sayBye monitor end.

以上 code 都可以通过 Github 中获取。

  • 本文作者: Yibo
  • 本文链接: https://windmt.com/2018/04/01/spring-aop-words-again/
  • 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!

再说 Spring AOP相关推荐

  1. Spring AOP + Redis解决重复提交的问题

    Spring AOP + Redis解决重复提交的问题 用户在点击操作的时候,可能会连续点击多次,虽然前端可以通过设置按钮的disable的属性来控制按钮不可连续点击,但是如果别人拿到请求进行模拟,依 ...

  2. 利用Spring AOP与JAVA注解为系统增加日志功能

    Spring AOP一直是Spring的一个比较有特色的功能,利用它可以在现有的代码的任何地方,嵌入我们所想的逻辑功能,并且不需要改变我们现有的代码结构. 鉴于此,现在的系统已经完成了所有的功能的开发 ...

  3. Spring AOP的一些概念

            切面(Aspect): 一个关注点的模块化,这个关注点可能会横切多个对象.事务管理是J2EE应用中一个关于横切关注点的很好的例子. 在Spring AOP中,切面可以使用通用类(基于模 ...

  4. Spring AOP与IOC

    Spring AOP实现日志服务 pom.xml需要的jar <dependency><groupId>org.apache.commons</groupId>&l ...

  5. Spring AOP与IOC以及自定义注解

    Spring AOP实现日志服务 pom.xml需要的jar <dependency><groupId>org.apache.commons</groupId>&l ...

  6. Spring Aop的应用

    2019独角兽企业重金招聘Python工程师标准>>> AOP的基本概念 连接点( Jointpoint) : 表示需要在程序中插入横切关注点的扩展点,连接点可能是类初始化.方法执行 ...

  7. Spring AOP详解(转载)所需要的包

    上一篇文章中,<Spring Aop详解(转载)>里的代码都可以运行,只是包比较多,中间缺少了几个相应的包,根据报错,几经百度搜索,终于补全了所有包. 截图如下: 在主测试类里面,有人怀疑 ...

  8. 关于spring aop Advisor排序问题

    关于spring aop Advisor排序问题 当我们使用多个Advisor的时候有时候需要排序,这时候可以用注解org.springframework.core.annotation.Order或 ...

  9. 利用spring aop统一处理异常和打日志

    利用spring aop统一处理异常和打日志 spring aop的概念,很早就写博客介绍了,现在在工作中真正使用. 我们很容易写出的代码 我们很容易写出带有很多try catch 和 logger. ...

  10. 我所理解的Spring AOP的基本概念

    Spring AOP中的概念晦涩难懂,读官方文档更是像读天书,看了很多例子后,写一些自己理解的一些spring的概念.要理解面向切面编程,要首先理解代理模式和动态代理模式. 假设一个OA系统中的一个功 ...

最新文章

  1. 手动部署OpenStack环境(五:新建网络及部署虚拟机)
  2. 列表list和numpy的ndarray的copy()函数
  3. 使用ssh-keygen和ssh-copy-id三步实现SSH无密码登录
  4. 【Groovy】编译时元编程 ( 编译时方法拦截 | 在 MyASTTransformation#visit 方法中进行方法拦截 )
  5. 【Qt】Qt5.12连接MySQl5.7(亲自测试成功)
  6. Codeforces 173E Camping Groups 线段树
  7. java编程九九乘法表_如何用JAVA语言编写一个九九乘法表
  8. Hulu诚意出品 |《百面深度学习》重磅上市
  9. linux下的vconfig配置_Linux系统下安装配置-OpenLDAP-phpLDAPadmin
  10. JS 全局函数 isNaN()、parseInt()、parseFloat()
  11. 算法7-16:弗洛伊德最短路径算法
  12. 将excel数据导入到SQL server数据库
  13. 信息学奥赛一本通知识集锦+往年真题
  14. 伪随机数的爆破--2
  15. JavaWeb图书馆管理系统
  16. OpenCV (c++)使用KDTree时,得到正确结果后报Segmentation fault (core dumped)
  17. 【《视觉SLAM十四讲》前ch2-ch6实践全过程和遇到的问题及解决办法】
  18. 【Linux云计算架构:第三阶段-Linux高级运维架构】第26章——tcp三次握手四次挥手及在局域网中使用 awl伪装MAC地址进行多线程SYN攻击
  19. 线性代数笔记——第一章行列式
  20. PCF8563模块不走时(海振电子PCF8653模块)

热门文章

  1. 文件操作---with语句
  2. Sublime Text Package Control错误另解
  3. Devexpress Barmanager设置
  4. ipsec ***概念(一)
  5. 数字图像处理实验(总计23个)汇总
  6. 在计算机系统中有两种不同的图像编码方式,第二章计算机系统与计算原理.ppt...
  7. 怎样不停请求接口实现实时刷新_Hologres+Flink实时数仓详解
  8. 中值滤波_Halcon联合C#编程自学笔记三之中值滤波
  9. python用psf函数_Python 嵌套函数(高级用法)
  10. Linux 打开/关闭CPU命令