一、概念

1、理论

把横切关注点和业务逻辑相分离是面向切面编程所要解决的问题。如果要重用通用功能的话,最常见的面向对象技术是继承(inheritance)或 组成(delegation)。但是,如果在整个应用中都使用相同的基类,继承往往会导致一个脆弱的对象体系;而使用组成可能需要对委托对象进行复杂的调用。切面提供了取代继承和委托的另一种可选方案,而且在很多场景下更清晰简洁。Spring AOP 基于动态代理,所以Spring只支持方法连接点,这与一些其他的AOP框架是不同的,例如AspectJ和JBoss,除了方法切点,它们还提供了字段和构造器接入点。

2、AOP术语

横切关注点(cross-cutuing concern):散布在应用中多处的功能。

切面(aspect) : 横切关注点模块化为特殊的类。切面是通知和切点的结合。

通知(advice):定义了切面是什么以及何时使用。

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

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

切点(pointcut):定义了切面在何处调用,会匹配通知所要织入的一个或多个连接点。
连接点(join point):在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。

织入(Weaving):织入是把切面应用到目标对象并创建新的代理对象的过程。

织入有三种方式可以实现,Spring采用的是第三种,在运行期织入的:

编译期:切面在目标类编译时被织入。这种方式需要特殊的编译器。AspectJ的织入编译器就是以这种方式织入切面的。
类加载期:切面在目标类加载到JVM时被织入。这种方式需要特殊的类加载器(ClassLoader),它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ 5的加载时织入(load-time weaving,LTW)就支持以这种方式织入切面。
运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。代理类封装了目标类,并拦截被通知方法的调用,再把调用转发给真正的目标bean。SpringAOP就是以这种方式织入切面的。

3、AspectJ的切点表达式语言

注意:只有execution指示器是实际执行匹配的,而其他的指示器都是用来限制匹配的。这说明execution指示器是我们在编写切点定义时最主要使用的指示器 。同时需要注意的是, 表达式之间允许用 &&(and)、||(or)、!(not) 来匹配复杂的被通知类。除了上面罗列的表达式外,Spring 还提供了一个Bean 表达式来匹配 Bean 的id,例如  execution(* com.service.Performance.perform(..)) && bean(performance)

@args的正确用法:自定义一个ElementType.TYPE的注解,这个注解用来修饰自定义类型(比如自己写的一个类),一个方法以这个自定义的类的实例为参数且只能有这唯一一参数,那这个方法在调用时会被匹配@args(自定义注解)的切面拦截。
@annotation的正确用法:在切面类上用@annotation加自定义注解就可以拦截使用这个注解的方法。比如匹配 @RequestMapping  注解的类  @annotation(org.springframework.web.bind.annotation.RequestMapping)

@target (cn.javass.spring.chapter6.Secure)  任何目标对象持有Secure注解的类方法;必须是在目标对象上声明这个注解,在接口上声明的对它不起作用。

二、使用注解创建切面

1、添加pom.xml依赖

  1. <dependency>
  2. <groupId>org.aspectj</groupId>
  3. <artifactId>aspectjrt</artifactId>
  4. <version>1.6.11</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.aspectj</groupId>
  8. <artifactId>aspectjweaver</artifactId>
  9. <version>1.6.11</version>
  10. </dependency>

2、定义切面

  1. @Aspect //表示这是一个切面类
  2. public class Audience {
  3.  
  4. //使用简明的PointCut
  5. @Pointcut("execution(* com.service.Performance.perform(..))")
  6. public void performance(){}
  7.  
  8. //前置通知 即 @Before("execution(* com.service.Performance.perform(..))")
  9. @Before("performance()")
  10. public void silenceCellPhones(){
  11. System.out.println("Silencing cell phones");
  12. }
  13. //前置通知 即 @Before("execution(* com.service.Performance.perform(..))")
  14. @Before("performance()")
  15. public void takeSeats(){
  16. System.out.println("Taking seats");
  17. }
  18.  
  19. //方法调用结束通知(并不是指返回值通知,即使是void的返回值,仍然会触发通知) 即 @AfterReturning("execution(* com.service.Performance.perform(..))")
  20. @AfterReturning("performance()")
  21. public void applause(){
  22. System.out.println("CLAP CLAP CLAP!!!");
  23. }
  24.  
  25. //有异常抛出的时候通知,即 @AfterThrowing("execution(* com.service.Performance.perform(..))")
  26. @AfterThrowing("performance()")
  27. public void demandRefund(){
  28. System.out.println("Demanding a refund");
  29. }
  30. }

3、启用AspectJ注解的自动代理

有两种方式可以启用AspectJ 注解的自动代理:

(1)在 Java 配置文件中显示配置

  1. @Configuration
  2. @EnableAspectJAutoProxy //启用Aop自动代理
  3. public class JavaConfig {
  4. @Bean
  5. public Audience getAudience(){
  6. return new Audience();
  7. }
  8. }

(2)在XML文件中配置

  1. <!--启用AspectJ自动代理-->
  2. <aop:aspectj-autoproxy/>
  3. <bean id="audience" class="com.aspect.Audience"/>

不管你是使用JavaConfig还是XML,AspectJ自动代理都会为使用@Aspect注解的bean创建一个代理,这个代理会围绕着所有该切面的切点所匹配的bean。当程序执行到连接点的时候,就会由代理转到切面触发相应的通知。

4、创建环绕通知

  1. @Aspect
  2. public class Audience3 {
  3.  
  4. @Pointcut("execution(* com.service.Performance.perform(..))")
  5. public void performance(){}
  6.  
  7. @Around("performance()")
  8. public void watchPerformance(ProceedingJoinPoint joinPoint) {
  9. System.out.println("Silencing cell phones");
  10. System.out.println("Taking seats");
  11. try {
  12. joinPoint.proceed();
  13. System.out.println("CLAP CLAP CLAP!!!");
  14. } catch (Throwable throwable) {
  15. System.out.println("Demanding a refund");
  16. throwable.printStackTrace();
  17. }
  18. }
  19. }

注意 ProceedingJoinPoint 作为参数。这个对象是必须要有的,因为你要在通知中通过它来调用被通知的方法。当要将控制权交给被通知的方法时,它需要调用ProceedingJoinPoint的proceed()方法。

5、切面匹配输入参数

  1. @Aspect
  2. public class TrackCounter {
  3.  
  4. private Map<Integer,Integer> trackCounts=new HashMap<Integer, Integer>();
  5.  
  6. //@Pointcut("execution(* com.service.CompactDisc.playTrack(int)) && args(trackNumber)") //带入输入参数
  7. //@Pointcut("target(com.service.CompactDisc) && args(trackNumber)") // target 匹配目标对象(非AOP对象)为指定类型
  8. //@Pointcut("within(com.service..*) && args(trackNumber)") //com.service 包以及子包下的所有方法都执行
  9. //@Pointcut("within(com.service..CompactDisc+) && args(trackNumber)") //com.service 包的CompactDisc类型以及子类型
  10. @Pointcut("this(com.service.CompactDisc) && args(trackNumber)") //匹配当前AOP代理对象类型,必须是类型全称,不支持通配符
  11. public void trackPlayed(int trackNumber){}
  12.  
  13. @Before("trackPlayed(trackNumber)")
  14. public void countTrack(int trackNumber){
  15. int playCount = getPlayCount(trackNumber);
  16. trackCounts.put(trackNumber,playCount+1);
  17. System.out.println(trackCounts.toString());
  18. }
  19.  
  20. public int getPlayCount(int trackNumber){
  21. return trackCounts.containsKey(trackNumber) ? trackCounts.get(trackNumber) : 0;
  22. }
  23. }
 参数的配置可以用占位符 *.. 
 * 的意思是任意类型任意名称的一个参数
 .. 的意思是任意类型,任意多个参数,并且只能放到args的后面。 

6、利用切面注入新功能

Java并不是动态语言。一旦类编译完成了,我们就很难为该类添加新的功能了。但是,我们的切面编程却可以做到动态的添加方法...话虽如此,其实也不过是障眼法罢了。实际上,面向切面编程,不过是把方法添加到切面代理中,当要对添加的方法调用的时候,可以把被通知的 Bean 转换成相应的接口。也就是代理会把此调用委托给实现了新接口的某个其他对象。实际上,一个bean的实现被拆分到了多个类中。(说实话,想了半天,实在想不到这个功能有什么作用......)

(1) 重新定义一个接口和实现类

  1. public interface Encoreable {
  2. void performEncode();
  3. }
  1. public class DefaultEncoreable implements Encoreable {
  2.  
  3. public void performEncode() {
  4. System.out.println("this is DefaultEncoreable");
  5. }
  6. }

(2) 把接口实现类嵌入到目标类代理中

  1. @Aspect
  2. public class EncoreableIntroducer {
  3.  
  4. @DeclareParents(value = "com.service.CompactDisc+",
  5. defaultImpl = DefaultEncoreable.class) //value 表示要嵌入哪些目标类的代理 。 defaultImpl:表示要嵌入的接口的默认实现方法
  6. public static Encoreable encoreable;
  7. }

(3) JUnit 测试

  1. @RunWith(SpringJUnit4ClassRunner.class)
  2. @ContextConfiguration(locations = "classpath:applicationContext.xml")
  3. public class Test02 {
  4.  
  5. @Autowired
  6. private CompactDisc compactDisc;
  7.  
  8. @Test
  9. public void test02(){
  10. compactDisc.playTrack(123);
  11. Encoreable compactDisc = (Encoreable) this.compactDisc; //当要调用添加的新功能的时候,这个用法相当于由代理转换到对应类实现,不会报类型转换错误
  12. compactDisc.performEncode();
  13.  
  14. }
  15. }

三、使用XML声明切面

1、定义切面

  1. public class AudienceXML {
  2.  
  3. public void silenceCellPhones(){
  4. System.out.println("Silencing cell phones");
  5. }
  6. public void takeSeats(){
  7. System.out.println("Taking seats");
  8. }
  9. public void applause(){
  10. System.out.println("CLAP CLAP CLAP!!!");
  11. }
  12. public void demandRefund(){
  13. System.out.println("Demanding a refund");
  14. }
  15. }

2、XML配置切面

  1. <aop:config>
  2. <aop:aspect ref="audienceXML">
  3. <aop:pointcut id="performance" expression="execution(* com.service.Performance.perform(..))"/>
  4. <aop:before method="silenceCellPhones" pointcut-ref="performance"/>
  5. <aop:before method="takeSeats" pointcut-ref="performance"/>
  6. <aop:after-returning method="applause" pointcut-ref="performance"/>
  7. <aop:after-throwing method="demandRefund" pointcut-ref="performance"/>
  8. </aop:aspect>
  9. </aop:config>

3、创建环绕通知

  1. public class Audience3XML {
  2.  
  3. public void watchPerformance(ProceedingJoinPoint joinPoint) {
  4. System.out.println("Silencing cell phones");
  5. System.out.println("Taking seats");
  6. try {
  7. joinPoint.proceed();
  8. System.out.println("CLAP CLAP CLAP!!!");
  9. } catch (Throwable throwable) {
  10. System.out.println("Demanding a refund");
  11. throwable.printStackTrace();
  12. }
  13. }
  14. }
  1. <aop:config>
  2. <aop:aspect ref="audience3XML">
  3. <aop:pointcut id="performance3" expression="execution(* com.service.Performance.perform(..))"/>
  4. <aop:around method="watchPerformance" pointcut-ref="performance3"/>
  5. </aop:aspect>
  6. </aop:config>

4、匹配输入参数

  1. <aop:config>
  2. <aop:aspect ref="trackCounter">
  3. <aop:pointcut id="trackPlayed" expression="execution(* com.service.CompactDisc.playTrack(int)) and args(trackNumber)"/>
  4. <aop:before method="countTrack" pointcut-ref="trackPlayed"/>
  5. </aop:aspect>
  6. </aop:config>

5、注入新功能

  1. <aop:config>
  2. <aop:aspect>
  3. <aop:declare-parents types-matching="com.service.CompactDisc+"
  4. implement-interface="com.service.Encoreable"
  5. default-impl="com.service.impl.DefaultEncoreable"
  6. delegate-ref="encoreableDelegate"/>
  7.  
  8. </aop:aspect>
  9. </aop:config>

再学习之Spring(面向切面编程)的更多相关文章

  1. 快速高效掌握企业级项目中的Spring面向切面编程应用,外带讲面试技巧

    Spring面向切面编程(AOP)是企业级应用的基石,可以这样说,如果大家要升级到高级程序员,这部分的知识必不可少. 这里我们将结合一些具体的案例来讲述这部分的知识,并且还将给出AOP部分的一些常见面 ...

  2. Spring 面向切面编程(AOP)

    Spring 系列教程 Spring 框架介绍 Spring 框架模块 Spring开发环境搭建(Eclipse) 创建一个简单的Spring应用 Spring 控制反转容器(Inversion of ...

  3. Spring学习笔记:面向切面编程AOP(Aspect Oriented Programming)

    一.面向切面编程AOP 目标:让我们可以“专心做事”,避免繁杂重复的功能编码 原理:将复杂的需求分解出不同方面,将公共功能集中解决 *****所谓面向切面编程,是一种通过预编译方式和运行期动态代理实现 ...

  4. spring框架学习(三)——AOP( 面向切面编程)

    AOP 即 Aspect Oriented Program 面向切面编程 首先,在面向切面编程的思想里面,把功能分为核心业务功能,和周边功能. 所谓的核心业务,比如登陆,增加数据,删除数据都叫核心业务 ...

  5. 详解Spring面向切面编程(AOP)三种实现

    一.什么是AOP AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善. ...

  6. Spring——面向切面编程(AOP)详解

    声明:本博客仅仅是一个初学者的学习记录.心得总结,其中肯定有许多错误,不具有参考价值,欢迎大佬指正,谢谢!想和我交流.一起学习.一起进步的朋友可以加我微信Liu__66666666 这是简单学习一遍之 ...

  7. Spring面向切面编程(AOP)

    1 spring容器中bean特性 Spring容器的javabean对象默认是单例的. 通过在xml文件中,配置可以使用某些对象为多列. Spring容器中的javabean对象默认是立即加载(立即 ...

  8. Spring面向切面编程(AOP,Aspect Oriented Programming)

    AOP为Aspect Oriented Programming的缩写,意为:面向切面编程(也叫面向方面),可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术. ...

  9. Spring面向切面编程

    在使用面向切面编程时,我们可以在一个地方定义通用的共鞥,但是可以通过声明的方式定义这个功能要以何种方式在何处应用,而无需修改受影响的类.横切关注点可以被模块化为特殊的类,这些类被称为切面.这样的优点是 ...

随机推荐

  1. ssh免密码记录

    主机器A通过ssh连多台从机器(b1,b2,b3). 1.使用root用户操作,避免权限问题. 2.在主从机器中安装ssh,命令: ssh-keygen –t rsa 然后都回车,生成的文件在/roo ...

  2. Lua5.3 注册表 _G _ENV

    Lua5.3 注册表 _G _ENV 来源:http://blog.csdn.net/murisly/article/details/46518551 注册表的描述,借用PIL中的一段话: regis ...

  3. 【每天半小时学框架】——React.js的模板语法与组件概念

           [重点提前说:组件化与虚拟DOM是React.js的核心理念!]        先抛出一个论题:在React.js中,JSX语法提倡将 HTML 和 CSS 全都写入到JavaScrip ...

  4. 关于C++中vector和set使用sort方法进行排序

    C++中vector和set都是非常方便的容器, sort方法是algorithm头文件里的一个标准函数,能进行高效的排序,默认是按元素从小到大排序 将sort方法用到vector和set中能实现多种 ...

  5. Flex中宽度计算

    flex 有三个属性值,分别是 flex-grow, flex-shrink, flex-basis,默认值是 0 1 auto. 发现网上详细介绍他们的文章比较少, 今天就详细说说他们,先一个一个看 ...

  6. DOM Exception error

    INDEX_SIZE_ERR  code 1                              索引是负值,或者超过了索引值 DOMSTRING_SIZE_ERR  code 2       ...

  7. asp.net应用发布到IIS无法链接到oracle数据库

    遇到这个问题纠结了好久,试了好多的方法,其中我的问题是,先安装了.net frameword4然后又安装的IIS. 正确方式应该是先安装IIS 然后安装.net framework;且应用程序池没有启 ...

  8. Scrum Meeting Alpha - 8

    Scrum Meeting Alpha - 8 NewTeam 2017/11/2 地点:新主楼F座二楼 任务反馈 团队成员 完成任务 计划任务 安万贺 完成了登录API的测试和包装Pull Requ ...

  9. C#用panel实现子窗体的切换

    今天编程的时候,遇到一个问题:在同一个窗体区域加载两个不同的窗体,每次只显示一个子窗体并能够对这两个子窗体做切换. 对于这个问题用panel控件是非常简单的,只要每次清空panel控件上的子窗体,然后 ...

  10. javaBean实体包区分

    随着软件工程结构越来越复杂,单一的entity实体包无法满足业务需要,可以采取PO,BO,DTO结构来分类实体. PO (Persistent Object):与数据库表数据关联的实体. BO(Bus ...