Spring之面向切面编程

  一、理解何为面向切面编程

    对于这个的理解,我觉得Spring实战中的例子讲得很明白:

    假设我现在是一个小区用户,每个月小区都要收电费,这时候就会来人查看电表,算出来这个月电费是多少,然后让我去结账。

    但在我很小的时候,其实电费是每个人家里有电表,要自己充电费,如果没电了就会断电,所以经常会出现在家里用着用着电,突然断电的情况,这时候就要下楼看电表,检查是没电费了还是跳闸了。

    电对家庭而言十分重要,哪里都离不开用电,但是我们更关注吃饭、睡觉、游戏等等跟我们密切相关的事情,检查电表虽然很重要,但这更像是一个被动且必须的操作,而第一种情况就是实现了切面式编程,把检查电表的操作提取了出来,变成一个统一的行为,我们不需要再自己去检查电表了,而是不管我们干什么,总会有人在对应的时间处理电费的问题,让我们可以更专心于与自己密切相关的事物中。

   二、AOP的实现

    对于通知织入,有动态织入和静态织入两种方法,他们的实现原理并不相同,我们来看一看两种实现方法的区别:

    静态织入:ApectJ采用的就是静态织入的方式。ApectJ主要采用的是编译期织入,在这个期间使用AspectJ的acj编译器(类似javac)把aspect类编译成class字节码后,在java目标类编译时织入,即先编译aspect类再编译目标类。

    具体流程大概如下图所示:

          

      对于更详细的说明,可以参照 https://blog.csdn.net/javazejian/article/details/56267036#oop%E7%9A%84%E6%96%B0%E7%94%9F%E6%9C%BA ,有很详细的说明

    动态织入:其原理主要是代理模式,如Java JDK的动态代理(Proxy,底层通过反射实现)或者CGLIB的动态代理(底层通过继承实现)。而Spring AOP的实现,也是基于动态代理的方法。因为Spring基于动态代理,所以Spring只支持方法连接点,对于字段、构造器连接点等,Spring无法实现,如果要实现对应功能,只能使用Aspect等来补充实现。

  

    三、Spring基于注解实现切面编程

    1、定义切点:

      我们新建一个表示表演的接口,来作为可以织入的连接点:

  1. public interface Performance {
  2. public void perform();
  3. }

            然后定义切点,一般使用execution()指示器,常见的格式为:

        

      除了可以定义切点以外,还可以通过其他指示器添加限定:         

        @within(com.xiaoxin.xxx)指示器表示只有对应的类上有对应的注解,才可以被代理增强。

        关于@Within更深一步的理解,可以参考 https://www.jianshu.com/p/fb109e03edec

        @Bean("id")指示器表示,只有是ID为对应id的的Bean对象执行方法的时候,才可以被代理增强。

      2、定义切面:

       首先定义一个Audience类,该类的方法会织入到perform()方法之中。

        在Spring AOP中,一共有五种通知,分别是:

       前置通知[Before advice]:在连接点前面执行,前置通知不会影响连接点的执行,除非此处抛出异常。 
       正常返回通知[After returning advice]:在连接点正常执行完成后执行,如果连接点抛出异常,则不会执行。 
       异常返回通知[After throwing advice]:在连接点抛出异常后执行。 
       返回通知[After (finally) advice]:在连接点执行完成后执行,不管是正常执行完成,还是抛出异常,都会执行返回通知中的内容。 
       环绕通知[Around advice]:环绕通知围绕在连接点前后,比如一个方法调用的前后。这是最强大的通知类型,能在方法调用前后自定义一些操作。环绕通知还需要负责决定是继续处理join point(调用ProceedingJoinPoint的proceed方法)还是中断执行。

       我们先看最基本的方法:

  1. @Aspect
  2. public class Audience {
  3. @Before("execution(* com.xiaoxin.Concert.Performance.perform(..))")
  4. public void TakeSeat(){
  5. System.out.println("Audience are taking seats");
  6. }
  7.  
  8. @AfterReturning("execution(* com.xiaoxin.Concert.Performance.perform(..))")
  9. public void applause(){
  10. System.out.println("Clap!");
  11. }
  12. @AfterThrowing("execution(* com.xiaoxin.Concert.Performance.perform(..))")
  13. public void demandFund(){
  14. System.out.println("Audience are asking for fund");
  15. }
  16. }

      这里我们直接把对应指示器的内容传入,但很明显发现一个问题,这里的切点都是一样的,都写一遍太麻烦了,所以可以有下面的改进方法:

  1. @Aspect
  2. public class Audience {
  3. @Pointcut("execution(* com.xiaoxin.Concert.Performance.perform(..))")
  4. public void performance(){}
  5.  
  6. @Before("performance()")
  7. public void TakeSeat(){
  8. System.out.println("Audience are taking seats");
  9. }
  10.  
  11. @AfterReturning("performance()")
  12. public void applause(){
  13. System.out.println("Clap!");
  14. }
  15. @AfterThrowing("performance()")
  16. public void demandFund(){
  17. System.out.println("Audience are asking for fund");
  18. }
  19. }

      直接定义切点,然后在后续定义通知时,直接使用建立的切点即可。

      与其他四个通知不同,环绕通知可以直接替代前置通知和后置通知,但使用方法有些许区别:

  1. @Around("performance()")
  2. public void aroundTest(ProceedingJoinPoint jp){
  3. try{
  4. System.out.println("Audience are taking seats Around");
  5. jp.proceed();
  6. System.out.println("Clap! Around");
  7. } catch (Throwable throwable) {
  8. throwable.printStackTrace();
  9. System.out.println("Audience are asking fund Around");
  10. }
  11.    }

      在这里,我们可以看到jp.proceed(),就是执行对应的方法,而前面执行前置通知,后面是成功返回的通知,catch里面对应是抛出异常的通知。

       现在定义好了切面和通知,但有一个问题就是,现在定义的通知,都是无参的,但比如我是查电表的,我需要获得电表数这样的参数,这时候需要怎么做呢?

       需要增加参数,使用args()指示器,现在添加一个售票的电影院类,而作为观众,需要再进入之前看看自己有没有足够的钱来购买电影票:

  1. public interface Cinema {
  2. public void sell(int money);
  3. } 
  1. @Component
  2. public class ChinaCinema implements Cinema {
  3. @Override
  4. public void sell(int money) {
  5. System.out.println("the price of ticket is "+money);
  6. }
  7. }

      现在已经添加好电影院类以后,增加切点和通知:

  1. @AfterReturning("execution(* com.xiaoxin.Concert.Cinema.sell(int)) &&args(money)")
  2. public void check(int money){
  3. System.out.println("i will check if i have money more than "+money);
  4. }

      这里的关注点是:&&angs(money)还有sell(int)这两个要搭配使用,前面加入int的参数后,在后面默认为这个的参数,这样就可以使用了。

      这里第三个问题就是上面的注解@Aspect,该注解表示Audience不仅仅是一个POJO,还是一个切面。Audience类中的方法都使用注解来定义切面的具体行为。

      但仅仅配置了@Aspect注解还不够,如果注解不被解析的话,Audience也只能被当做一个普通的Java类来使用,因此我们需要对切面的解析进行配置。

    3、配置自动代理:

      配置既可以使用java配置类来实现,也可以使用xml文件来实现:

      对于Java配置类,可以用如下注解实现:

  1. @Configuration
  2. @EnableAspectJAutoProxy
  3. @ComponentScan
  4. public class ConcertConfig {
  5. @Bean
  6. public Audience audience(){
  7. return new Audience();
  8. }
  9. }

      前面几个注解已经很熟悉了,@Configuration表示这是一个注解类,@ComponentScan表示自动扫描该配置类所在包的所有内容,里面的@Bean注解,表示注册了一个Audience的对象进入Spring容器,唯一没见过的就是这个@EnableAspectJAutoProxy注解,这个注解表示启用自动代理。

      如果使用xml配置,那么xml文件应该如此:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"
  3. xmlns:context="http://www.springframework.org/schema/context"
  4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  5. xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop">
  6.  
  7. <context:component-scan base-package="com.xiaoxin.Concert"/>
  8. <aop:aspectj-autoproxy/>
  9. <bean class="com.xiaoxin.Concert.Audience"/>
  10. </beans>

      4、测试使用

  1. @RunWith(SpringJUnit4ClassRunner.class)
  2. @ContextConfiguration(classes = com.xiaoxin.Concert.ConcertConfig.class)
  3. public class AOPTest {
  4. @Autowired
  5. Performance artist;
  6. @Autowired
  7. Cinema chinaCinema;
  8. @Test
  9. public void test(){
  10. chinaCinema.sell(50);
  11. //这里刚刚犯的一个很大的错误:不应该直接new 而是从容器里面获得建立好的Bean对象
  12. artist.perform();
  13. }
  14. }

      这里要不然就获得ApplicationContext(应用上下文对象)来获得容器里的Bean,要不然就使用@Autowired来自动装配对应对象,不要在里面直接new,这样new出来的是新的对象,但AOP的原理是代理机制,是获得出来的对象不再是之前的类对象,而是经过代理增强后的对象,因此不能直接new,必须要从Spring容器中获取才可以。

    四、Spring基于xml文件配置切面:

       其实配置方法大同小异,就是把注解中的@Component变为一个<bean>标签声明,然后把切面方法都声明在<aop:config>中:

  1. <aop:aspectj-autoproxy/>
  2. <bean id="artist" class="com.xiaoxin.ConcertXml.Artist"/>
  3. <bean id="audience" class="com.xiaoxin.ConcertXml.Audience"/>
  4. <bean id="cinema" class="com.xiaoxin.ConcertXml.ChinaCinema"/>
  5. <bean id="DefaultEncoreable" class="com.xiaoxin.ConcertXml.DefaultEncoreable"/>
  6. <aop:config>
  7. <aop:aspect ref="audience">
  8. <aop:pointcut id="perform" expression="execution(* com.xiaoxin.ConcertXml.Performance.perform(..))"/>
  9. <aop:before method="TakeSeat" pointcut-ref="perform"/>
  10. <aop:before method="check" pointcut="execution(* com.xiaoxin.ConcertXml.Cinema.sell(int)) and args(money)"/>
  11. </aop:aspect>
  12.  
  13. </aop:config>

      这里要注意的就是:不要忘记配置自动代理。

      用xml配置的好处就是,在有些时候,我们是不能修改源代码来增加注解的,比如从其他地方下载的jar包等,这时候就要用xml配置来完成切面的装配。

      

      

Spring学习笔记(三):面向切面的Spring的更多相关文章

  1. Spring实战第四章学习笔记————面向切面的Spring

    Spring实战第四章学习笔记----面向切面的Spring 什么是面向切面的编程 我们把影响应用多处的功能描述为横切关注点.比如安全就是一个横切关注点,应用中许多方法都会涉及安全规则.而切面可以帮我 ...

  2. Spring使用笔记(四) 面向切面的Spring

    面向切面的Spring 一.面向切面的概念 在软件开发中,散布于应用多处的功能被称为横切关注点(cross-cutting concern). 通常来讲这些横切关注带点从概念上来讲是与应用逻辑相分离的 ...

  3. Spring学习(四)--面向切面的Spring

    一.Spring--面向切面 在软件开发中,散布于应用中多处的功能被称为横切关注点(cross- cutting concern).通常来讲,这些横切关注点从概念上是与应用的业 务逻辑相分离的(但是往 ...

  4. 第04章-面向切面的Spring

    1. 什么是面向切面编程 AOP是什么 切面帮助我们模块化横切关注点. 横切关注点可被描述为影响应用[多处的]功能.如安全,应用许多方法会涉及安全规则. 继承与委托是最常见的实现重用 通用功能 的面向 ...

  5. 五、面向切面的spring(1)

    spring的依赖注入看完了,接下来是spring中与DI一并重要的AOP了,开始吧,GO. 在软件开发中,散布于应用中多处的功能被称为横切发关注点,通常来讲,这些横切关注点从概念上市与应用的业务逻辑 ...

  6. 面向切面的Spring

    在软件开发中,发布于应用中多处的功能被称为横切关注点.通常,这些横切关注点从概念上是与应用的业务逻辑相分离的(但往往直接嵌入到应用的业务逻辑之中).将横切关注点与业务逻辑相分离是AOP所要解决的. 一 ...

  7. Spring AOP 面向切面的Spring

    定义AOP术语 描述切面的常用术语有: 通知 (advice) 切点 (pointcut) 连接点 (joinpoint) 下图展示了这些概念是如何关联的 Spring 对AOP的支持 Spring提 ...

  8. Spring学习笔记(1)——初识Spring

    一.Spring是什么       通常说的Spring其实指的是Spring Framework,它是Spring下的一个子项目,Spring围绕Spring Framework这个核心项目开发了大 ...

  9. Spring学习笔记(二) 初探Spring

    版权声明 笔记出自<Spring 开发指南>一书. Spring 初探 前面我们简单介绍了 Spring 的基本组件和功能,现在我们来看一个简单示例: Person接口Person接口定义 ...

随机推荐

  1. INDIRECT函数实现动态图表的跨数据抓取

    涉及函数: indirect函数:通常有两种用法.直接指定单元格地址和隐式指定单元格地址.直接指定:=indirect("A4"),则会返回A4单元格所显示的内容.参数给定的既是字 ...

  2. 共享文件夹下其他文件可以访问但php文件访问不了的原因

    刚开始的问题是在virtualbox里的共享文件夹下的项目运行不了,原因是宝塔下nginx的用户和用户组默认是www 和 www 需要改成www vboxsf(因为自动挂载的目录为/media/sf_ ...

  3. Spring Security OAuth2 笔记(一)

    关于 refresh_token refresh_token 主要是用来在 access_token 快要过期的时候,对 access_token 进行一个刷新,生成一个新的 access_token ...

  4. Web 之 Cookie

    Cookie Cookie实际上是一小段的文本信息.客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie.客户端浏览器会把Cookie保存起来.当浏 ...

  5. 初篇:我与Linux

        据悉,红帽认证将于本年的8月份更换Rhel7为Rhel8.所以我想趁这次机会搏一搏.     我个人是初中就神仰Linux已久,只不过那个时候的我只知道Linux系统,不知道有什么区分.奈何那 ...

  6. 01-复杂度2 Maximum Subsequence Sum

    01-复杂度2 Maximum Subsequence Sum   (25分) 时间限制:200ms 内存限制:64MB 代码长度限制:16kB 判题程序:系统默认 作者:陈越 单位:浙江大学 htt ...

  7. CF思维联系– CodeForces -CodeForces - 992C Nastya and a Wardrobe(欧拉降幂+快速幂)

    Nastya received a gift on New Year - a magic wardrobe. It is magic because in the end of each month ...

  8. 我想solo自己一个人!

    区域赛之后你就该走了,现在你告诉我,没精力不打了,我真谢谢你! 今年就TM的没有一点舒心的地方! 父母分居, 队友出走, 队伍解散, 白天家里两个外甥很吵, 鼻窦炎复发, 喜欢的妹子也追不到, 整夜失 ...

  9. CF--思维练习--CodeForces - 221C-H - Little Elephant and Problem (思维)

    ACM思维题训练集合 The Little Elephant has got a problem - somebody has been touching his sorted by non-decr ...

  10. 图论--边双连通V-DCC缩点

    // tarjan算法求无向图的割点.点双连通分量并缩点 #include<iostream> #include<cstdio> #include<cstring> ...