2019独角兽企业重金招聘Python工程师标准>>>

前言

在软件开发中,散布于应用中多处的功能被称为横切关注点。这些横切关注点从概念上是与应用的业务逻辑相分离的。把这些横切关注点与业务逻辑相分离正是面向切面编程(AOP)所要解决的问题。AspectJ---另一种流行的AOP实现。 如果是要重用通用功能的话,最常见的面向对象技术是继承或委托。但是,如果在整个应用中都使用相同的基类,继承往往会导致一个脆弱的对象体系;而使用委托可能需要对委托对象进行复杂的调用。

AOP

通知(Advice):定义了切面是什么以及何时使用。除了描述切面要完成的工作,通知还解决了何时执行这个工作的问题。

Spring切面可以应用五种类型的通知:

  • 前置通知(Before):在目标方法被调用之前调用通知功能。
  • 后置通知(After):在目标方法完成之后调用通知,次数不会关心方法的输出是什么。
  • 返回通知(After-returning):在目标方法成功执行之后调用通知。
  • 异常通知(After-throwing):在目标方法抛出异常后调用通知。
  • 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。

连接点(Join Point):在用于执行过程中能够插入切面的一个点。切面代码可以利用这些点插入到应用的正常流程中,并添加新的行为。

切点(PointCut):切点的定义会匹配通知所有织入的一个或多个连接点。通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。

切面(Aspect):切面是通知和切点的结合,通知和切点共同定义了切面的全部内容--它是什么,在何时和何处完成其功能。

引入(Introduction):引入允许我们向现有的类添加新方法或属性。

织入(Weaving):织入是把切面应用到目标对象并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中。在目标对象的生命周期里有多个点可以进行织入:

  1. 编译期:切面在目标类编译时被织入,这种方式需要特殊的编译器。AspectJ的织入编译器就是以这种方式织入切面的。

2.类加载期:切面在目标类加载到JVM时被织入。这种方式需要特殊的类加载器(ClssLoader),他可以在目标类被引入应用之前增强该目标类的字节码。

3.运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。

Spring对AOP的支持

Spring提供了4中类型的AOP支持。 1.基于代理的经典SpringAOP。

2.纯POJO切面。

3.@AspectJ注解驱动的切面。

4.注入式AspectJ切面

通过切点来选择连接点

1.编写切点

首先:定义一个HelloWorld接口:

public interface HelloWorld
{void printHelloWorld();void doPrint();
}

切点的配置

execution(* com.xrq.aop.HelloWorld.(..)) 切点表达式。execution 在方法执行时触发, com.xrq.aop.HelloWorld.(..) 筛选制定的方法。 com.xrq.aop.HelloWorld.(..)) 是连接点。??

<aop:config><aop:aspect id="time" ref="timeHandler"><aop:pointcut id="addAllMethod" expression="execution(* com.xrq.aop.HelloWorld.*(..))" /><aop:before method="printTime" pointcut-ref="addAllMethod" /><aop:after method="printTime" pointcut-ref="addAllMethod" /></aop:aspect></aop:config>

2.使用注解创建切面

使用@AspectJ注解进行了标注,该注解表明 Advices 不仅是一个POJO类,也是一个切面。 使用@PoinCut定义命名的切点,可以简化重复的代码。

package com.zhangguo.Spring052.aop02;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;/*** 通知类,横切逻辑**/
@Component
@Aspect
public class Advices {@Before("execution(* com.zhangguo.Spring052.aop02.Math.*(..))")public void before(JoinPoint jp){System.out.println("----------前置通知----------");System.out.println(jp.getSignature().getName());}@After("execution(* com.zhangguo.Spring052.aop02.Math.*(..))")public void after(JoinPoint jp){System.out.println("----------最终通知----------");}
}

2.JavaConfig启用切面的代理 在配置类的级别上通过使用@EnableAspectJAutoProxy 注解启动代理功能。

package com.zhangguo.Spring052.aop05;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;@Configuration  //用于表示当前类为容器的配置类,类似<beans/>
@ComponentScan(basePackages="com.zhangguo.Spring052.aop05")  //扫描的范围,相当于xml配置的结点<context:component-scan/>
@EnableAspectJAutoProxy(proxyTargetClass=true)  //自动代理,相当于<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
public class ApplicationCfg {//在配置中声明一个bean,相当于<bean id=getUser class="com.zhangguo.Spring052.aop05.User"/>@Beanpublic User getUser(){return new User();}
}

3.xml装配Bean,启动注解: aspectj-autoproxy 元素声明aop的启动。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-4.3.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-4.3.xsd"><context:component-scan base-package="com.zhangguo.Spring052.aop02"></context:component-scan><aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
</beans>

环绕通知 Around

环绕通知接受 ProceedingJoinPoint作为参数。这个对象是必须要有的,因为你要在通知中通过它来调用被通知的方法。通知方法中可以做任何的事情,当要将控制权交给被通知的方法时,它需要调用ProceedingJoinPoint的Proceed()方法。

如果忘记调用Proceed()方法,通知实际上会阻塞对被通知方法的调用。也可以在通知中对它进行多长调用,实现充实逻辑。

package com.zhangguo.Spring052.aop04;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;/*** 通知类,横切逻辑*/
@Component
@Aspect
public class Advices {//切点@Pointcut("execution(* com.zhangguo.Spring052.aop04.Math.a*(..))")public void pointcut(){}//前置通知@Before("pointcut()")public void before(JoinPoint jp){System.out.println(jp.getSignature().getName());System.out.println("----------前置通知----------");}//最终通知@After("pointcut()")public void after(JoinPoint jp){System.out.println("----------最终通知----------");}//环绕通知@Around("execution(* com.zhangguo.Spring052.aop04.Math.s*(..))")public Object around(ProceedingJoinPoint pjp) throws Throwable{System.out.println(pjp.getSignature().getName());System.out.println("----------环绕前置----------");Object result=pjp.proceed();System.out.println("----------环绕后置----------");return result;}//返回结果通知@AfterReturning(pointcut="execution(* com.zhangguo.Spring052.aop04.Math.m*(..))",returning="result")public void afterReturning(JoinPoint jp,Object result){System.out.println(jp.getSignature().getName());System.out.println("结果是:"+result);System.out.println("----------返回结果----------");}//异常后通知@AfterThrowing(pointcut="execution(* com.zhangguo.Spring052.aop04.Math.d*(..))",throwing="exp")public void afterThrowing(JoinPoint jp,Exception exp){System.out.println(jp.getSignature().getName());System.out.println("异常消息:"+exp.getMessage());System.out.println("----------异常通知----------");}
}

4 通过注解引入新功能

@DeclareParents注解:

  • value 属性指定了那种类型的bean要引入该接口。
  • DefaultImpl 属性指定了为引入功能提供实现的类。
  • @DeclareParents注解所标注的静态属性指明了要引入的接口。

面向注解的切面声明有一个明显的劣势:你必须能够为通知类添加注解。如果你没有源码的话,或者不想将AspectJ注解放入到代码中,SpringXml可以实现。

4.4 在xml中声明切面

优先的原则:基于注解的配置要优于基于Java的配置,基于Java的配置要优于基于XMl的配置。 基于xml的配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-4.3.xsd"><!-- 被代理对象 --><bean id="math" class="com.zhangguo.Spring052.aop01.Math"></bean><!-- 通知 --><bean id="advices" class="com.zhangguo.Spring052.aop01.Advices"></bean><!-- aop配置 --><aop:config proxy-target-class="true"><!--切面 --><aop:aspect ref="advices"><!-- 切点 --><aop:pointcut expression="execution(* com.zhangguo.Spring052.aop01.Math.*(..))" id="pointcut1"/><!--连接通知方法与切点 --><aop:before method="before" pointcut-ref="pointcut1"/><aop:after method="after" pointcut-ref="pointcut1"/></aop:aspect></aop:config></beans>

注入AspectJ切面

当我们需要创建更细粒度的通知或想监测bean的创建时,Spring所支持的AOP就比较弱了,这时,可以选择使用AspectJ提供的构造器切点,并且可以借助Spring的依赖注入把bean装配进AspectJ切面中。下面就来举个栗子:

//定义表演接口
package concert;
public interface Performance {void perform(); void finishPerform(String performer, String title);
}
//定义钢琴表演
package concert;
public class PianoPerform implements Performance {public PianoPerform(){System.out.println("有请钢琴表演");}@Overridepublic void perform() {System.out.println("钢琴表演开始");}@Overridepublic void finishPerform(String performer, String title) {System.out.println(performer + "演奏钢琴曲:" + title);}
}
//定义小提琴表演
package concert;
public class ViolinPerform implements Performance {public ViolinPerform(){System.out.println("有请小提琴表演");}@Overridepublic void perform() {System.out.println("小提琴表演开始");}@Overridepublic void finishPerform(String performer, String title){System.out.println(performer + "演奏了小提琴曲:" + title);}
}
//定义工作人员,将作为切面的协作bean
package concert;
public class Worker {public void take(){System.out.println("观众已全部交出手机");}public void sendMsg(String name){System.out.println(name + "表演即将开始,请各位观众交出手机");}public void broadcast(String performer, String title){System.out.println(performer + "演奏完毕,刚才演奏的曲子叫:" + title);}
}
//定义切面
package concert;
public aspect Audience {    private Worker worker;public Audience(){}//通过setter方法注入public void setWorker(Worker worker){this.worker = worker;System.out.println("工作人员已入场");}//定义piano构造器切点和后置通知pointcut piano():execution(concert.PianoPerform.new());after():piano(){worker.sendMsg("钢琴");}//定义violin构造器切点和后置通知pointcut violin():execution(concert.ViolinPerform.new());after():violin(){worker.sendMsg("小提琴");}//定义不带参数方法切点和前置通知pointcut perform():execution(* concert.Performance.perform());before():perform(){worker.take();}//定义带两个参数的切点和后置通知pointcut finishPerform(String performer, String title):execution(* concert.Performance.finishPerform(String, String)) && args(performer, title);after(String performer, String title):finishPerform(performer, title){worker.broadcast(performer, title);}
}

XML配置文件:spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><!--<bean id="piano" class="concert.PianoPerform" lazy-init="true"/>--><bean id="worker" class="concert.Worker"/><!--Spring需要通过静态方法aspectOf获得audience实例,Audience切面编译后的class文件附在文末--><bean class="concert.Audience" factory-method="aspectOf"><property name="worker" ref="worker" /><!--通过Spring把协作的bean注入到切面中--></bean><!--这里注意一下bean的顺序,因为在构造器切点后置通知时调用了worker的sendMsg(String)方法,所以避免出现空指针异常,咱们先把worker声明在前--><!--如果要将piano或者violin声明在前,可以设置lazy-init="true"--><!--所以spring是从上到下解析并实例化bean?还是解析完整个文件再实例化呢?欢迎评论区留言交流--><bean id="piano" class="concert.PianoPerform"/><bean id="violin" class="concert.ViolinPerform"/>
</beans>
//主程序
package concert;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainClass {public static void main(String[] args) {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring.xml");Performance piano = context.getBean("piano", Performance.class);piano.perform();piano.finishPerform("亚莎·海菲兹", "致爱丽斯");Performance violin = context.getBean("violin", Performance.class);violin.perform();violin.finishPerform("霍洛维茨", "爱之喜悦");}}
<dependencies><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>4.3.8.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-core</artifactId><version>4.3.8.RELEASE</version></dependency>       <dependency><groupId>org.springframework</groupId><artifactId>spring-beans</artifactId><version>4.3.8.RELEASE</version></dependency>        <dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>4.3.8.RELEASE</version></dependency>        <dependency><groupId>org.aspectj</groupId><artifactId>aspectjrt</artifactId><version>1.8.7</version></dependency></dependencies><build><plugins><!--编译aspect的插件,必须要将Audience.aj编译为class文件,不然spring创建audience bean的时候找不到类--><plugin><groupId>org.codehaus.mojo</groupId><artifactId>aspectj-maven-plugin</artifactId><version>1.8</version><executions><execution><goals><goal>compile</goal></goals></execution></executions><configuration><complianceLevel>1.8</complianceLevel><source>1.8</source><target>1.8</target></configuration></plugin></plugins></build>

Audience.class文件

package concert;import concert.Worker;
import org.aspectj.lang.NoAspectBoundException;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;@Aspect
public class Audience {private Worker worker;static {try {ajc$postClinit();} catch (Throwable var1) {ajc$initFailureCause = var1;}}public void setWorker(Worker worker) {this.worker = worker;System.out.println("工作人员已入场");}public Audience() {}@After(value = "piano()",argNames = "")public void ajc$after$concert_Audience$1$dd71540a() {this.worker.sendMsg("钢琴");}@After(value = "violin()",argNames = "")public void ajc$after$concert_Audience$2$57c630b6() {this.worker.sendMsg("小提琴");}@Before(value = "perform()",argNames = "")public void ajc$before$concert_Audience$3$1cad9822() {this.worker.take();}@After(value = "finishPerform(performer, title)",argNames = "performer,title")public void ajc$after$concert_Audience$4$1840cdb9(String performer, String title) {this.worker.broadcast(performer, title);}//Aspect提供的静态方法,返回Audience切面的一个实例,Spring即可通过factory-method属性获得该实例//<bean class="concert.Audience" factory-method="aspectOf">public static Audience aspectOf() {if(ajc$perSingletonInstance == null) {throw new NoAspectBoundException("concert_Audience", ajc$initFailureCause);} else {return ajc$perSingletonInstance;}}public static boolean hasAspect() {return ajc$perSingletonInstance != null;}
}

写在最后: Spring所支持的AOP已经可以满足很多需求,如果要求更高,可以使用AspectJ提供的更丰富的切点类型,当然需要熟悉AspectJ语法。 本文只是简单的举出了部分切点类型和通知类型,更多的类型读者可以自行尝试。欢迎留言指正,感谢您的阅读!

转载于:https://my.oschina.net/u/3421984/blog/1607526

面向切面的Spring相关推荐

  1. spring(4)面向切面的Spring(AOP)

    [0]README 1)本文部分文字描述转自:"Spring In Action(中/英文版)",旨在review  "spring(4)面向切面的Spring(AOP) ...

  2. 《Spring实战》读书笔记-第4章 面向切面的Spring

    <Spring实战>是学习Spring框架的一本非常经典的书籍,之前阅读了这本书,只是在书本上写写画画,最近整理了一下<Spring实战>的读书笔记,通过博客的方式进行记录分享 ...

  3. 面向切面的 Spring —— 什么是面向切面编程?

    Q1:面向切面编程(AOP)解决什么问题? A1:把横切关注点与业务逻辑相分离. Q2:什么是横切关注点? A2:在软件开发中,散布于应用中多处的功能. 日志是应用切面的常见范例,但并不是切面适用的唯 ...

  4. spring boot 503_Spring实战读书笔记第4章 面向切面的Spring

    本章内容: 面向切面编程的基本原理 通过POJO创建切面 使用@AspectJ注解 为AspectJ切面注入依赖 在软件开发中,散布于应用中多的功能被称为横切关注点(cross-cutting con ...

  5. Spring实战 | 第一部分 Spring的核心(第四章 面向切面的Spring)

    第四章 面向切面编程 面向切面编程的基本原理 通过POJO创建切面 使用@AspectJ注解 为AspectJ切面注入依赖 AspectJ是一个面向切面的框架,它扩展了java语言.AspectJ定义 ...

  6. 【Spring实战学习笔记】第4章 面向切面的Spring

    目录 4.1 什么是面向切面编程 4.2 通过切点来选择连接点 4.3 使用注解创建切面 4.4 在XML中声明切面 4.5 注入AspectJ切面 4.6 小结 在软件开发中,散布于应用中多处的功 ...

  7. Spring4详解系列(四)面向切面的Spring

    1.什么是面向切面编程 AOP(Aspect-Oriented Programming), 即面向切面编程, 它与 OOP( Object-Oriented Programming, 面向对象编程) ...

  8. Spring In Action 03 ---面向切面的Spring

    在系统中有些功能我们需要应用到程序的多个地方,但是我们又不想在每个点都明确的调用他.日志.安全和事务管理的确很重要,但他们是否是应用对象主动参与的行为呢?如果让应用对象只关注于自己所针对的业务领域问题 ...

  9. iOS面向切面的TableView-AOPTableView

    iOS面向切面的TableView-AOPTableView 这个是公司很久之前的开源项目,一个大牛写的,在项目中一直有在用,今天有空发了点时间看下如何实现,看了之后感觉挺有收获,故撰此文,分享给需要 ...

最新文章

  1. linux数据库什么意思,Linux系统中的数据库命令是什么
  2. AT91RM9200+pcf8563在linux2.6.20下的调试心得
  3. RAD Studio (Delphi) Firemonkey 教程
  4. 解决win10安装MySQL数据库出现服务无法启动的问题
  5. LCP 01. Guess Numbers 猜数字
  6. Linux系统如何测试电脑性能,如何使用DD命令测试Linux磁盘的性能?
  7. 00005在java结果输出_Java-005-运算符详解
  8. python管理数据库设计_Pycharm+Django+Python+MySQL开发 后台管理数据库
  9. 【HDU - 1856】 More is better(并查集)(还需要空间优化。。)
  10. PHP中功能强大却少使用的函数 -- 为你提供更多的思路
  11. 奇偶链表的分割(C++)
  12. Java并发编程-多线程基础
  13. iOS开发NSDate、NSString、时间戳之间的转化
  14. 揭秘淘宝286亿海量图片存储与处理架构(转)
  15. 淘客链接转成正常淘宝链接JAVA代码实现
  16. 关于 Hypervisor的理解
  17. AttributeError: module ‘torch‘ has no attribute ‘inference_mode‘
  18. teamViewer远程连接vnp过程
  19. 浅谈OpenNI之我见
  20. upc Buy an Integer#二分

热门文章

  1. asynchronous vs non-blocking
  2. Python数据结构————二叉查找树的实现
  3. 3G手机Android应用开发视频教程_黎活明老师的视频(第五天课程)总共有八天课程...
  4. Eclipse 调试器
  5. MDaemon邮件服务器解决方案之应急恢复解决方案
  6. 怎样在javascript中直接设置好打印方式为横向或纵向(測試未果)
  7. JavaScript 多线程概念的含义
  8. 编写访问数据库的应用层程序,经常catch出的一些错误
  9. AndroidStudio_安卓原生开发_获取系统S/N序列号_实现在springboot服务端_设备远程保活监控---Android原生开发工作笔记159
  10. 大数据之-Hadoop3.x_MapReduce_序列化案例需求分析---大数据之hadoop3.x工作笔记0096