Spring AOP 实现原理以及应用

AOP简单介绍

一、什么是AOP

1OOP面向对象编程是我们最常用的编程思想,要实现一个业务逻辑,我们要对每个对象进行逐一操作;而AOP,面向切面编程,则完全不同,AOP可以针对某一类特定的对象,进行统一切开,然后写入统一的代码。

   如下图所示,这样就避免了OOP模式中,需要对每个类或者方法去添加同样的代码。

 110721_omds_2485991.png

2、在平时开发中我们会用到很多标签,这些标签就是SpringAOP的一种应用方式。例如喜闻乐见的事务声明标签@Transactional,就是通过AOP来实现的.

    如果没有AOP,我们需要在每个事务方法中,自己写上一堆代码进行事务控制,这会让人直接崩溃。所以AOP能大大的简化我们的编程方式,以及提升代码的可读性和可维护性。

110759_Lljq_2485991.png

 

二、AOP的使用方式

1Spring提供了4种实现AOP的方式:

1)经典的基于代理的AOP

2@AspectJ注解驱动的切面

3)纯POJO切面

4)注入式AspectJ切面

 

2、首先看经典的基于代理的AOP:

 Spring支持五种类型的通知:

1Before()  org.apringframework.aop.MethodBeforeAdvice

2After-returning(返回后) org.springframework.aop.AfterReturningAdvice

3After-throwing(抛出后) org.springframework.aop.ThrowsAdvice

4Arround(周围) org.aopaliance.intercept.MethodInterceptor

5Introduction(引入) org.springframework.aop.IntroductionInterceptor

  

   使用方法:

   1)创建通知类:实现这几个接口,把其中的方法实现了,在实现方法中描述我们需要织入切面的业务逻辑代码

2)定义切点和通知者:在Spring配制文件中配置这些信息

3)使用ProxyFactoryBean来生成代理

 

举例说明:

1)首先我们定义一个接口,然后实现它:

 

110905_gBam_2485991.png

110905_cYdn_2485991.png

  2)编写一个SleepHelper类,它里面包含了睡觉的辅助工作,AOP术语来说它就应该是通知,我们这里要实现前置和返回两个接口,并且实现其中的方法,并且在Spring配置文件中进行配置:

110940_EAHU_2485991.png

110940_qihj_2485991.png

   3)配置一个切点,常用的配置方式有两种:1.使用正则表达式 2.使用AspectJ表达式。下面我们配置了所有包含sleep名称的方法作为一个切点:

110957_0k72_2485991.png

   4)将通知和切点结合起来:

111011_K0V1_2485991.png

   5)调用ProxyFactoryBean产生代理对象

111051_m9c0_2485991.png

   6)测试,我们使用接口定义并且获取代理对象,然后调用sleep方法,获得程序运行结果:

   111031_4ESO_2485991.png

111051_YSzP_2485991.png

通过上面的例子可以了解SpringAOP的基本实现方式,这种方式可以很形象的理解AOP是怎么实现的。

三、SpringAOP的原理

1、代理模式:

Spring在加载的过程中,会扫描各个bean对应的class,并且生成各自对应的代理对象。生成代理时会根据不同的切面织入我们需要的代码,这就是SpringAOP的实现方式。

 

按照代理类的创建时期,代理类可分为两种。 静态代理类: 由程序员创建或由特定工具自动生成源代码,再对其编译。在程序运行前,代理类 .class 文件就已经存在了。 动态代理类:在程序运行时,运用反射机制动态创建而成。

 

 

111202_geaf_2485991.png

 

 

2、源码分析

   简单看一下Spring源码中是怎么实现AOP代理的:

 

1)  Spring的配置文件中,所有的切面和通知器都必须定义在 <aop:config> 元素内部。springaop namespacehandlerAopNamespaceHandler。其初始化代码如下:

111223_SwA7_2485991.png

 

2)  可以看到,aop:config标签的解析类是:ConfigBeanDefinitionParser,其parse方法如下,其中实现了两个主要功能:

A、注册一个AspectJAwareAdvisorAutoProxyCreator类型的bean

B、解析主标签下面的advisor标签,并且注册advisor

111240_CGAy_2485991.png

 

3)  下面单独分析这两个功能,首先看AspectJAwareAdvisorAutoProxyCreator 类型bean 的注册,跟踪一下方法调用,我们可以发现使用到了AopConfigUtils类的registerOrEscalateApcAsRequired方法:

111315_ncU0_2485991.png

111315_CBqO_2485991.png

111315_kfAK_2485991.png

111316_x5HN_2485991.png

 

3)注意参数里面的clsAspectJAwareAdvisorAutoProxyCreator.class,这个是在前面把调用委托过来的时候直接写死的。这段代码注册了一个名为AUTO_PROXY_CREATOR_BEAN_NAME(org.springframework.aop.config.internalAutoProxyCreator)beanbean的类型是AspectJAwareAdvisorAutoProxyCreator

AspectJAwareAdvisorAutoProxyCreator到底有什么呢,看一下继承关系,可以发现实现了InstantiationAwareBeanPostProcessor接口。

111407_DOwv_2485991.png

111407_56Av_2485991.png

111407_ptWl_2485991.png

 

4)  InstantiationAwareBeanPostProcessor接口是BeanPostProcessor的一个子类,在bean初始化的时候调用。此接口的实现在AbstractAutoProxyCreator中。这段代码的作用就是每当bean初始化前,检查是否需要生成代理对象。如果需要,就生成代理。

111407_YDsa_2485991.png

5)  上面的代码中,我们可以看到生成代理的逻辑里,首先需要获取当前bean相关联的Advisor,也就是AOP中的通知对象类。获取的思路是先找到所有实现了Advisor接口的bean,然后根据配置文件中的advisor配置从中挑出能匹配到当前beanadvisor

6)  获取到advisor之后,就是生成proxy了,根据之前获取到的advisorbeanClass生成一个代理bean。这也就完成了我们第2)步说的注册一个AspectJAwareAdvisorAutoProxyCreator类型的bean

7)下面分析一下第2)步中提到的第二个方面:

解析主标签下面的advisor标签,并且注册advisor

这个解析是在ConfigBeanDefinitionParse里面完成的,首先根据配置标签的advisor-id属性,生成一个beanclass DefaultBeanFactoryPointcutAdvisoradvisor对象,然后把对象的id注册到parserContext里面:

111508_LGAi_2485991.png

 

7)  之前我们提到获取当前bean相关联的Advisor,正好就是从上一步生成和注册的内容里面获取的。

 

8)  综合上面的内容,Spring AOP的原理大致如下:

   配置一个实现了InstantiationAwareBeanPostProcessor接口的bean。这个beanSpring初始化容器里面各个bean的时候会被调用,调用后会找到所有AOP通知类,根据AOP切面配置,判断是不是需要为即将实例化的bean生成代理,如果需要,就把对应的advice编制在代理对象里面。这样就实现了动态代理的功能。

   流程图如下:

111526_xNMJ_2485991.png

四、基于注解的实际应用场景

  1、业务场景

     在某个整合项目中,我们需要把三个子系统结合为一个统一推广平台,使用同一个帐号进行登录,并且区分不同的平台权限。这就带来一个问题,怎么防止一个用户登录后,手动使用URL访问其它的页面或者数据?

 

     如果我们在系统的每一个方法里面都加上权限查询和判断代码,无疑会给开发和维护带来很大的麻烦。如果以后新增的方法忘了添加这些代码,会给系统带来安全隐患。

 

  2、解决方案

     针对上面这种业务逻辑,我们可以使用AOP的方式,对所有方法进行统一的权限管理。这里我们使用Spring注解的方式来实现AOP

 

  3、实现方式

     1)首先我们定义两个标签,分别用来标注跳转方法,和ResponseBody数据返回方法。标签的参数采用ElementType.METHOD表示标签在方法上面使用,RetentionPolicy.RUNTIME表示系统运行时才进行动态代理。

112301_VT1Q_2485991.png

112301_58ii_2485991.png

 

     2)定义切面,直接使用上面定义的标签来标注我们需要进行权限控制的方法,跳转方法使用@UnionWebUserRedirectAnnotation ,如果是返回值的方法,则使用@UnionWebUserResponseBodyAnnotation

  

112324_US6D_2485991.png

112324_n6vq_2485991.png

 

 

     3)定义通知内容,通过@Aspect标签指定通知类,在通知类里面我们直接定义通知方法的类型,以及切面。

      下图中@Around表示环绕型通知,参数UnionWebUserResponseBodyAnnotation表示所有被@UnionWebUserResponseBodyAnnotation注解标注的方法,都进行AOP代理。

      在环绕型通知方法里面,我们首先获取被标注方法的参数point,然后运行原始方法获得返回值。再判断用户的权限,是否拥有网盟用户权限,如果有的话就返回结果,如果没有权限,则返回null

      同样的,针对112400_QzeW_2485991.png@UnionWebUserRedirectAnnotation标签进行环绕通知,判断用户的属性,如果没有权限,那么进行重定向跳转。

112415_df02_2485991.png

 

4、运行效果

   1)正常用户访问系统,不会有任何影响。

   2)当用户请求跳转url的时候,如果没有对应的系统权限,那么会重定向跳转到首页。

   3)当用户请求获取数据url的时候,如果没有对应系统的权限,那么会返回null

五、AOP应用场景

1、列举一些AOP实际使用中能够解决的一些问题:

Authentication 权限

Caching 缓存

Context passing 内容传递

Error handling 错误处理

Lazy loading 懒加载

Debugging 调试

logging, tracing, profiling and monitoring 记录跟踪 优化 校准

Performance optimization 性能优化

Persistence 持久化

Resource pooling 资源池

Synchronization 同步

Transactions 事务

 

六、总结

   在实际项目中,没有一种设计模式能够解决所有的问题,我们要根据实际情况,采用合适的设计模式来解决各种问题。

   AOP面向切面编程,使用跟OOP完全不同的思想,很好的解决了OOP难以实现的一些功能。在实际使用中,我们可以根据需要,将两者结合,灵活运用,让我们的程序更加完美。

转载于:https://my.oschina.net/u/2485991/blog/533117

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值