E、作用

AOP是面向对象编程的一个强大补充。通过AOP,我们现在可以把之前分散在应用各处的行为放入可重用的模块中。我们显示地声明在何处如何应用该行为。这有效减少了代码冗余,并让我们的类关注自身的主要功能。

一、概念

1、横切关注点

  • 散布于应用中多处的功能被称为横切关注点(cross- cutting concern)
  • 通常来讲,这些横切关注点从概念上是与应用的业务逻辑相分离的,但是往往会直接嵌入到应用的业务逻辑之中

2、面向切面编程

在使用面向切面编程时,我们仍然在一个地方定义通用功能,但是可以通过声明的方式定义这个功能要以何种方式在何处应

3、AOP术语

(1)通知(Advice

         1)定义:切面所要做的工作被称为通知
         2)负责:定义切面需要完成什么工作+何时执行工作(方法执行前?后?....),包含了需要用于多个应用对象的横切行为;
         3)spring切面支持通知类型:
前置通知(Before):
在目标方法被调用之前调用通知功能;
后置通知(After):
在目标方法完成之后调用通知,此时不会关
心方法的输出是什么;
返回通知(After-returning):
在目标方法成功执行之后调用通
知;
异常通知(After-throwing)
在目标方法抛出异常后调用通知;
环绕通知(Around)
通知包裹了被通知的方法,在被通知的方
法调用之前和调用之后执行自定义的行为。

(2)连接点(Join point

1)定义:应用通知的时机被称为连接点。

2)负责:连接点是在应用执行过程中能够插入切面的一个点,这个点可以是调用方法时抛出异常时、甚至修改一个字段时

(3)切点(Poincut

1)定义:(有表达式那个)一个切面并不需要通知应用到所有连接点。切点有助于缩小切面所通知的连接点的范围。

2)负责:何处应用切面,切点的定义会匹配通知所要织入的一个或多个连接点;切点定义了通知被应用的 具体位置(在哪些连接点);也就是定义了哪些连接点会得到通知

3)通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。有些AOP框架允许我们创建动态的切点,可以根据运行时的决策(比如方法的参数值)来决定是否应用通知。

(4)切面(Aspect

1)定义:切面是通知和切点的结合

2)负责:通知和切点共同定义了切面的全部内容——它是什么,在何时和何处完成其功能。

(5)引入(Introduction

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

(5)织入(Weaving

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

2)说明:切面在指定的连接点被织入到目标对象中。在目标对象的生命周期里有多个点可以进行织入:

  • 编译期:切面在目标类编译时被织入。这种方式需要特殊的编译。AspectJ的织入编译器就是以这种方式织入切面的。
  • 类加载期:切面在目标类加载到JVM时被织入。这种方式需要殊的类加载器(ClassLoader),它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ 5的加载时织入(load-time weaving,LTW)就支持以这种方式织入切面。
  • 运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。Spring AOP就是以这种方式织入切面的。

二、解决问题

把这些横切关注点与业务逻辑相分离正是面向切面编程(AOP)所要解决的问题。DI有助于应用对象之间的解耦,而AOP可以较好的实现【横切关注点与它们所影响的对象之间的解耦】

如果使用面向对象技术解决【横切关注点与它们所影响的对象之间的解耦】,比如继承、委托,存在问题:

1、使用继承:如果在整个应用中都使用相同的基类,继承往往会导致一个脆弱的对象体系;

2、使用委托:可能需要对委托对象进行复杂的调用。

三、适用场景

1、日志

2、声明式事务

3、安全

4、缓存

四、Spring AOP知识点

1、Spring AOP种类

(前3个都是Spring AOP变体:基于代理的AOP)

  • 基于代理的经典Spring AOP:(已经过时)
  • Spring声明式AOP,基于纯POJO切面:(Spring的aop命名空间,需要XML配置)
  • @AspectJ注解驱动的切面;依然是Spring基于代理的AOP,编程风格与AspectJ注解基本一致(能够不使用XML配置):(模仿AspectJ注解)
  • 注入式AspectJ切面(适用于Spring各版本):如果你的AOP需求超过了简单的方法拦截需求,比如需要构造器或属性拦截,那么你需要考虑使用AspectJ来实现切面:(AspectJ的AOP)

2、Spring AOP局限

由于Spring AOP构建在动态代理基础之上,因此,Spring对AOP的支持局限于方法拦截

3、Spring AOP框架的一些关键知识

  • Spring通知是Java编写的
  • Spring在运行时通知对象
  • Spring只支持方法连接点:因为Spring基于动态代理
  • Spring不支持字段和构造器连接点:AspectJ和JBoss支持,方法连接点可以满足大部分需求,如需要方法拦截之外的连
    接点,则使用Aspect来补充Spring AOP的功能

五、SpringAOP 实践部分

1、Spring的声明式AOP

1)定义切点

方式:在Spring AOP中,要使用AspectJ的切点表达式语言来定义切点。(如何具体使用切点,见创建切面部分)

说明:Spring仅支持AspectJ的切点表达式的一个子集,因为Spring是基于代理的,而某些切点表达式是基于属性、构造器的

支持的AspectJ的切点表达式(俗称:AspectJ的切点指示器),如下表:

AspectJ
示器
描 述
arg()
限制连接点匹配方法的参数为指定类型
@args()
限制连接点匹配方法的参数由指定注解标注
execution()
匹配的连接点类型是:方法
this()
限制连接点匹配AOP代理的bean引用为指定类型的类
target
限制连接点匹配目标对象为指定类型的类
@target()
限制连接点匹配特定的执行对象,这些对象对应的类要具有指定类 型的注解
within()
限制连接点匹配指定的类型,限制连接点仅匹配指定的包
@within()
限制连接点匹配指定注解所标注的类型(当使用Spring AOP时,方法定义在由指定的注解所标注的类里)
@annotation
限定匹配带有指定注解的连接点
bean('bean的ID或bean的名称')
在切点表达式中使用bean的ID或bean名称来标识bean,来限制切点只匹配特定的bean
这个指示器是spring增加的,不是AspectJ中的
  • execution是实际执行匹配的,而其他的指示器都是用来限制匹配的。
  • 这说明execution指示器是我们在编写切点定义时最主要使用的指示器。在此基础上,我们使用其他指示器来限制所匹配的切点。

示例:

上面示例中,方法表达式以“*”号开始,表明了我们不关心方法返回值的类型。然后,我们指定了全限定类名和方法名。对于方法参数列表,我 们使用两个点号(..)表明切点要选择任意的perform()方法,无论该方法的入参是什么。
  • 可以使用&&、||、!  操作符(也可以在SpringXML中直接使用and、or、not代替这三个字符)组合多个切点指示器,分别为与、或、非关系,切点必须匹配所有的指示器

示例说明:再之前配置的基础上 and 配置的切点仅匹配concert包中bean

  • bean('bean的ID或bean的名称')的示例:

示例说明:在执行Performance的perform()方法时应用通知,但限定bean的ID为woodstock。

2)创建切面(基于注解)(通知、切面、切点结合使用)

  • 基于       【POJO类+类上@AspectJ注解+方法上@各种通知注解 】  可以轻松实现切面的定义
  • Spring使用AspectJ注解来声明通知的注解:因此需要引入AspectJ依赖
<dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>${springframe.version}</version>
</dependency>
  • Spring支持的AspectJ中通知注解如下:
注 解
通 知
@After
通知方法会在目标方法返回或抛出异常后调用,相当于finally
@AfterReturning
通知方法会在目标方法返回后调用
@AfterThrowing
通知方法会在目标方法抛出异常后调用
@Around
通知方法会将目标方法封装起来
@Before
通知方法会在目标方法调用之前执行
  • 在spring中设置【启用自动代理功能】:

    • 使用JavaConfig的话:在配置类的类级别上通过使用EnableAspectJ-AutoProxy注解启用自动代理功能
    • 使用XML来装配bean的话:使用Spring aop命名空间中的<aop:aspectj-autoproxy>元素,见下图:

  • (非常重要)Spring的AspectJ自动代理仅仅使用@AspectJ作为创建切面的指导(也就是虽然使用的是AspectJ相关注解:@AspectJ、@Pointcut、@各种通知注解),切面依然是基于代理的。在本质上,它依然是Spring基于代理的切面。这一点非常重要,因为这意味着尽管使用的是@AspectJ注解,但我们仍然限于代理方法的调用。如果想利用AspectJ的所有能力,我们必须在运行时使用AspectJ并且不依赖Spring来创建基于代理的切面
  • 面向注解的切面缺点:面向注解的切面声明有一个明显的劣势:你必须能够为通知类添加注解。为了做到这一点,必须要有源码

代码示例(示例中bean的装配采用JavaConfig方式):

业务bean接口:

package com.mzj.springframework.aop._01_SpringDeclarativeAOP.bean;/*** 业务bean接口:演出*/
public interface Performance {public void perform();
}

业务bean实现:

package com.mzj.springframework.aop._01_SpringDeclarativeAOP.bean;import org.springframework.stereotype.Component;/*** 业务bean实现** @Auther: mazhongjia* @Date: 2020/3/23 13:33* @Version: 1.0*/
@Component
public class PerformanceImpl implements Performance {@Overridepublic void perform() {System.out.println("PerformanceImpl....");}
}

切面示例一:缺点(相同的切点表达式我们重复了四遍)

package com.mzj.springframework.aop._01_SpringDeclarativeAOP.bean;import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;/*** 切面** 使用@AspectJ注解进行了标注。该注解表 明Audience不仅仅是一个POJO,还是一个切面** @Auther: mazhongjia* @Date: 2020/3/23 12:26* @Version: 1.0*/
//@Component
@Aspect
public class Audience1 {/*** 手机静音** 通知注解+切点表达式*/@Before("execution(** com.mzj.springframework.aop._01_SpringDeclarativeAOP.bean.Performance.perform(..))")//表演之前public void silenceCellPhones(){System.out.println("Silenceing cell phones1");}/*** 就坐*/@Before("execution(** com.mzj.springframework.aop._01_SpringDeclarativeAOP.bean.Performance.perform(..))")//表演之前public void takeSeats(){System.out.println("Taking seats1");}/*** 鼓掌*/@AfterReturning("execution(** com.mzj.springframework.aop._01_SpringDeclarativeAOP.bean.Performance.perform(..))")//表演完成后public void applause(){System.out.println("CLAP CLAP CLAP1!!!");}/*** 退款*/@AfterThrowing("execution(** com.mzj.springframework.aop._01_SpringDeclarativeAOP.bean.Performance.perform(..))")//表演失败之后public void demandRefund(){System.out.println("Demanding a refund1");}
}

切面示例二:

package com.mzj.springframework.aop._01_SpringDeclarativeAOP.bean;import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;/*** 切面** 使用@AspectJ注解进行了标注。该注解表 明Audience不仅仅是一个POJO,还是一个切面** 通过将相同的切点统一声明的方式来优化Audience1** @Auther: mazhongjia* @Date: 2020/3/23 12:26* @Version: 1.0*/
@Component
@Aspect
public class Audience2 {/*** 定义切点** 加@Pointcut注解,我们实际上扩展了切点表达式语言,这样就可 以在任何的切点表达式中使用performance()了*/@Pointcut("execution(** com.mzj.springframework.aop._01_SpringDeclarativeAOP.bean.Performance.perform(..))")//**与 com.mzj之间必须有空格public void performance(){//performance()方法的实际内容并不重要}@Before("performance()")public void silenceCellPhones(){System.out.println("Silenceing cell phones2");}@Before("performance()")public void takeSeats(){System.out.println("Taking seats2");}@AfterReturning("performance()")public void applause(){System.out.println("CLAP CLAP CLAP2!!!");}@AfterThrowing("performance()")public void demandRefund(){System.out.println("Demanding a refund2");}
}

JavaConfig方式:

package com.mzj.springframework.aop._01_SpringDeclarativeAOP.bean;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
@ComponentScan
@EnableAspectJAutoProxy//在配置类的类级别上通过使 用EnableAspectJ-AutoProxy注解启用自动代理功能
public class ConcertConfig {/*** 使用自动扫描:ComponentScan,并不会自动创建切面,所以需要通过@Bean进行创建  或者 在切面类增加@Component注解也行~~~~* @return*/@Beanpublic Audience1 audience1() {return new Audience1();}//    @Bean
//    public Audience2 audience2() {
//        return new Audience2();
//    }
}

测试类:

package com.mzj.springframework.aop;import com.mzj.springframework.aop._01_SpringDeclarativeAOP.bean.ConcertConfig;
import com.mzj.springframework.aop._01_SpringDeclarativeAOP.bean.Performance;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes= ConcertConfig.class)
public class Test {@Autowiredprivate Performance performance;@org.junit.Testpublic void play() {performance.perform();}}

运行结果:

2-1)Around环绕通知

  • 通过切面中环绕通知方法的ProceedingJoinPoint参数调用业务方法,仅环绕通知有这个参数
package com.mzj.springframework.aop._01_SpringDeclarativeAOP.bean.around;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;/*** 切面** 环绕通知** @Auther: mazhongjia* @Date: 2020/3/23 12:26* @Version: 1.0*/
@Aspect
public class Audience4Around {@Pointcut("execution(** com.mzj.springframework.aop._01_SpringDeclarativeAOP.bean.Performance.perform(..)))")public void performance(){};@Around("performance()")public void watchPerformance(ProceedingJoinPoint joinPoint){//ProceedingJoinPoint是在通知中通过它来调用被通知的方法try {System.out.println("around start.....");//有意思的是,你可以不调用proceed()方法,从而阻止对被通知方 法的访问//也可以在通知中对它进行多次调用。要这样 做的一个场景就是实现重试逻辑joinPoint.proceed();System.out.println("around end.....");} catch (Throwable throwable) {System.out.println("around exception.....");}}
}

2-2)通知中参数(从命名切点到通知方法的参数转移)

  • 在切点表达式中声明参数,将这个参数传入到通知方法中

    • 声明了要提供给通知方法的参数:其中方法参数位不再是接收任意类型参数的(..)了,而是接收参数的类型(多个参数以逗号分隔)
    • 切点表达式增加【指定参数】限定符部分:表明传递给sing()方法的String、String类型参数也会传递到通知中去:&& args(参数),这个参数对应@Pointcut注解修饰的方法参数

  • 详细说明如下图:
语法说明:切点表达式中的args(trackNumber)限定符。它表明传递给playTrack()方法的int类型参数也会传递到通知中去。参数的名称trackNumber也与切点方法签名中的参数相匹配。
  • 在通知方法的注解(@Before之类)调用切点方法时设置传入参数,这个参数与通知方法参数名称对应、与切点表达式中参数顺序一致但是参数名不需要一致

====按照上面几点规则就完成了从命名切点到 通知方法的参数转移====

代码示例如下:
业务bean类:
package com.mzj.springframework.aop._01_SpringDeclarativeAOP.bean.adviceArgs;public interface SingASong {void sing(String SongName,String SongContext);
}

业务bean实现类:

package com.mzj.springframework.aop._01_SpringDeclarativeAOP.bean.adviceArgs;
import org.springframework.stereotype.Component;@Component
public class SingASongImpl implements SingASong {@Overridepublic void sing(String songName, String songContext) {System.out.println("SingASong " + songName + " : " + songContext);}
}

切面类:

package com.mzj.springframework.aop._01_SpringDeclarativeAOP.bean.adviceArgs;import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;import java.util.ArrayList;
import java.util.List;/*** 切面** 从命名切点到 通知方法的参数转移** @Auther: mazhongjia* @Date: 2020/3/23 12:26* @Version: 1.0*/
@Aspect
public class Audience4AroundArgs {private int songCount = 0;private List<String> songList = new ArrayList<>();@Pointcut("execution(* com.mzj.springframework.aop._01_SpringDeclarativeAOP.bean.adviceArgs.SingASong.sing(String,String))" + "&& args(songName,songContext)")public void trackPlayed(String songName,String songContext){};@Before("trackPlayed(songName,songContext)")public void countTrack(String songName,String songContext){songCount++;songList.add(songName);System.out.println("已经唱了" + songCount + "首歌曲了,他们是:" + songList);}
}

JavaConfig类:

package com.mzj.springframework.aop._01_SpringDeclarativeAOP.bean.adviceArgs;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;/*** @Auther: mazhongjia* @Date: 2020/3/24 13:11* @Version: 1.0*/
@Configuration
@ComponentScan
@EnableAspectJAutoProxy
public class TrackCounterConfig {@Beanpublic SingASong singASong(){return new SingASongImpl();}@Beanpublic Audience4AroundArgs audience1() {return new Audience4AroundArgs();}
}

测试类:

package com.mzj.springframework.aop;import com.mzj.springframework.aop._01_SpringDeclarativeAOP.bean.adviceArgs.SingASong;
import com.mzj.springframework.aop._01_SpringDeclarativeAOP.bean.adviceArgs.TrackCounterConfig;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;/*** @Auther: mazhongjia* @Date: 2020/3/24 14:13* @Version: 1.0*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes= TrackCounterConfig.class)
public class AdviceTest {@Autowiredprivate SingASong singASong;@org.junit.Testpublic void songTest() {String song1 = "let it go...let it go...";String song2 = "我爱你中国....我爱你中国....";singASong.sing("Let It Go",song1);singASong.sing("我爱你中国",song2);}
}

运行结果:

已经唱了1首歌曲了,他们是:[Let It Go]
SingASong Let It Go : let it go...let it go...
已经唱了2首歌曲了,他们是:[Let It Go, 我爱你中国]
SingASong 我爱你中国 : 我爱你中国....我爱你中国....

2-3)通过注解引入新功能

目标说明:方法包装仅仅是切面所能实现的功能之一,通过编写切面,可以为被通知的对象引入全新的功能

原理:使用Spring AOP,可以为bean引入新的方法。代理拦截调用并委托给实现该方法的其他对象,实际上,一个bean的实现被拆分到了多个类中。
方式:
  • 为新功能定义一个接口,并增加功能对应实现类
  • 创建一个切面
  • 切面中定义一个public static变量,变量类型为新增功能接口
  • 为变量添加注解:@DeclareParents注解,将新功能接口引入到准备增加功能的目标bean中
  • 和其他的切面一样,需要在Spring中将切面类声明为一个bean(JavaConfig中使用@Bean注解到一个创建切面的方法,或者使用<bean>元素)
示例代码:
业务bean接口
package com.mzj.springframework.aop._01_SpringDeclarativeAOP.bean.NewFeature;/*** 目标bean接口*/
public interface SingASong {void sing(String SongName, String SongContext);
}

业务bean实现

package com.mzj.springframework.aop._01_SpringDeclarativeAOP.bean.NewFeature;
import org.springframework.stereotype.Component;/*** 目标bean实现*/
@Component
public class SingASongImpl implements SingASong {@Overridepublic void sing(String songName, String songContext) {System.out.println("SingASong " + songName + " : " + songContext);}
}

准备在业务bean增加的新功能接口

package com.mzj.springframework.aop._01_SpringDeclarativeAOP.bean.NewFeature;/*** 增加的新功能接口** @Auther: mazhongjia* @Date: 2020/3/24 17:55* @Version: 1.0*/
public interface NewFeature {void newFeature();
}

新功能实现

package com.mzj.springframework.aop._01_SpringDeclarativeAOP.bean.NewFeature;/*** 增加的新功能实现** @Auther: mazhongjia* @Date: 2020/3/24 17:55* @Version: 1.0*/
public class NewFeatureImpl implements NewFeature{@Overridepublic void newFeature() {System.out.println("增加新功能。。。");}
}

JavaConfig

package com.mzj.springframework.aop._01_SpringDeclarativeAOP.bean.NewFeature;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;/*** JavaConfig类*/
@Configuration
@ComponentScan
@EnableAspectJAutoProxy
public class NewFeatureConfig {@Beanpublic NewFeatureAspect newFeatureAspect() {return new NewFeatureAspect();}}

切面

package com.mzj.springframework.aop._01_SpringDeclarativeAOP.bean.NewFeature;import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;/*** 切面** @Auther: mazhongjia* @Date: 2020/3/24 17:57* @Version: 1.0*/
@Aspect
public class NewFeatureAspect {/*** 通过@DeclareParents注解,将NewFeature接口引入到NewFeature bean中** @DeclareParents注解由三部分组成:*      1)value属性指定了哪种类型的bean要引入该接口。在本例中,也 就是所有实现Performance的类型。(标记符后面的加号表示 是Performance的所有子类型,而不是Performance本 身。)*      2)defaultImpl属性指定了为引入功能提供实现的类。在这里, 我们指定的是DefaultEncoreable提供实现。*      3)@DeclareParents注解所标注的静态属性指明了要引入了接 口。在这里,我们所引入的是Encoreable接口。*/@DeclareParents(value="com.mzj.springframework.aop._01_SpringDeclarativeAOP.bean.NewFeature.SingASong+",defaultImpl = NewFeatureImpl.class)public static NewFeature newFeature;
}

测试代码:使用新功能时,需要先强制转换成新功能接口

package com.mzj.springframework.aop;import com.mzj.springframework.aop._01_SpringDeclarativeAOP.bean.NewFeature.NewFeature;
import com.mzj.springframework.aop._01_SpringDeclarativeAOP.bean.NewFeature.NewFeatureConfig;
import com.mzj.springframework.aop._01_SpringDeclarativeAOP.bean.NewFeature.SingASong;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;/*** @Auther: mazhongjia* @Date: 2020/3/24 14:13* @Version: 1.0*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes= NewFeatureConfig.class)
public class NewFeatureTest {@Autowiredprivate SingASong singASong;@org.junit.Testpublic void songTest() {String song1 = "let it go...let it go...";String song2 = "我爱你中国....我爱你中国....";singASong.sing("Let It Go",song1);singASong.sing("我爱你中国",song2);((NewFeature)singASong).newFeature();}
}
Spring的自动代理机制将会获取到它的声明,当Spring发现一个bean使用了@Aspect注解时,Spring就会创建一个代理,然后将调用委托给被代理的bean或被引入的实现,这取决于调用的方法属于被代理的bean还是属于被引入的接口。

运行结果:

SingASong Let It Go : let it go...let it go...
SingASong 我爱你中国 : 我爱你中国....我爱你中国....
增加新功能。。。

3)创建切面(基于XML)(通知、切面、切点结合使用)

使用说明:基于注解的配置要优于基于Java的配置,基于Java的配置要优于基于XML的配置。但是,如果你需要声明切面,但是又不能为通知类添加注解的时候,那么就必须转向XML配置了。
SpringAOP配置元素能够以非侵入性的方式声明切面
AOP配置元素
用 途
<aop:advisor>
定义AOP通知器
<aop:after>
定义AOP后置通知(不管被通知的方法是否执行成功)
<aop:after-
returning>
定义AOP返回通知
<aop:after-
throwing>
定义AOP异常通知
<aop:around>
定义AOP环绕通知
<aop:aspect>
定义一个切面
<aop:aspectj-
autoproxy>
启用@AspectJ注解驱动的切面
<aop:before>
定义一个AOP前置通知
<aop:config>
顶层的AOP配置元素。大多数的<aop:*>元素必须包含
在<aop:config>元素内
<aop:declare-
parents>
以透明的方式为被通知的对象引入额外的接口
<aop:pointcut>
定义一个切点

3-1)before、after通知

代码示例:

业务bean接口:

package com.mzj.springframework.aop._02_XMLAOP;/*** 业务bean接口:演出*/
public interface Performance {public void perform();
}

业务bean实现:

package com.mzj.springframework.aop._02_XMLAOP;import org.springframework.stereotype.Component;/*** 业务bean实现** @Auther: mazhongjia* @Date: 2020/3/23 13:33* @Version: 1.0*/
public class PerformanceImpl implements Performance {@Overridepublic void perform() {System.out.println("PerformanceImpl....");}
}

POJO切面类:

package com.mzj.springframework.aop._02_XMLAOP;import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;/***  将一个POJO定义为一个切面*** @Auther: mazhongjia* @Date: 2020/3/23 12:26* @Version: 1.0*/
public class Audience2 {public void silenceCellPhones(){System.out.println("Silenceing cell phones223");}public void takeSeats(){System.out.println("Taking seats223");}public void applause(){System.out.println("CLAP CLAP CLAP223!!!");}public void demandRefund(){System.out.println("Demanding a refund223");}
}

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:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://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"><!--业务bean--><bean id="performance" class="com.mzj.springframework.aop._02_XMLAOP.PerformanceImpl"/><!--切面bean--><bean id="audience2" class="com.mzj.springframework.aop._02_XMLAOP.Audience2"/><aop:config><!--切面类,ref属性指向POJO切面类--><aop:aspect ref="audience2"><!--method:切面类中通知方法,pointcut:切入点(使用AspectJ切点表达式语法所定义的切点。)--><aop:before method="silenceCellPhones" pointcut="execution(** com.mzj.springframework.aop._02_XMLAOP.Performance.perform(..))"></aop:before><aop:before method="takeSeats" pointcut="execution(** com.mzj.springframework.aop._02_XMLAOP.Performance.perform(..))"></aop:before><aop:after-returning method="applause" pointcut="execution(** com.mzj.springframework.aop._02_XMLAOP.Performance.perform(..))"></aop:after-returning><aop:after-throwing method="demandRefund" pointcut="execution(** com.mzj.springframework.aop._02_XMLAOP.Performance.perform(..))"></aop:after-throwing></aop:aspect></aop:config>
</beans>

测试类:

package com.mzj.springframework.aop.xmlaop;import com.mzj.springframework.aop._02_XMLAOP.Performance;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;/*** @Auther: mazhongjia* @Date: 2020/3/25 13:04* @Version: 1.0*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath*:com/mzj/springframework/aop/_02_XMLAOP/XMLAOP.xml" })
public class BeforeAfterTest {@Autowiredprivate Performance performance;@Testpublic void play() {performance.perform();}
}

执行结果:

Silenceing cell phones223
Taking seats223
PerformanceImpl....
CLAP CLAP CLAP223!!!

可以统一定义切点,在不同通知中使用:

<?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:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://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"><!--业务bean--><bean id="performance" class="com.mzj.springframework.aop._02_XMLAOP.PerformanceImpl"/><!--切面bean--><bean id="audience2" class="com.mzj.springframework.aop._02_XMLAOP.Audience2"/><aop:config><!--定义切点--><aop:pointcut id="performance-cut" expression="execution(** com.mzj.springframework.aop._02_XMLAOP.Performance.perform(..))"/><!--切面类,ref属性指向POJO切面类--><aop:aspect ref="audience2"><!--method:切面类中通知方法,pointcut:切入点(使用AspectJ切点表达式语法所定义的切点。)--><aop:before method="silenceCellPhones" pointcut-ref="performance-cut"></aop:before><aop:before method="takeSeats" pointcut-ref="performance-cut"></aop:before><aop:after-returning method="applause" pointcut-ref="performance-cut"></aop:after-returning><aop:after-throwing method="demandRefund" pointcut-ref="performance-cut"></aop:after-throwing></aop:aspect></aop:config>
</beans>

可以把切点<aop:pointcut>定义在<aop:aspect>内部,也可以把切点定义在<aop:config>内部,区别是范围可以使用的范围不同。

3-2)环绕通知

说明:如果不使用成员变量存储信息的话,在前置通知和后置通知之间共享信息非常麻烦,唯一方式是在前置通知所在的切面类中使用一个成员变量记录信息,在后置通知中使用这个变量记录的值。因为bean默认是单例的,如果像这样保存 状态的话,将会存在线程安全问题。 存在这种需求时,环绕通知在这点上有明显的优势。

代码示例:

业务bean:使用之前前置后置通知的bean

切面类:

package com.mzj.springframework.aop._02_XMLAOP.around;import org.aspectj.lang.ProceedingJoinPoint;/***  将一个POJO定义为一个切面*** @Auther: mazhongjia* @Date: 2020/3/23 12:26* @Version: 1.0*/
public class Audience2 {public void watchPerformance(ProceedingJoinPoint joinPoint){try{System.out.println("表演之前.....");joinPoint.proceed();//执行被通知的方法System.out.println("表演成功后.....");}catch (Throwable e){System.out.println("表演失败后....");}}}

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:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://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"><!--业务bean--><bean id="performance" class="com.mzj.springframework.aop._02_XMLAOP.PerformanceImpl"/><!--普通bean--><bean id="audience2" class="com.mzj.springframework.aop._02_XMLAOP.around.Audience2"/><aop:config><!--定义切点--><aop:pointcut id="performance-cut" expression="execution(** com.mzj.springframework.aop._02_XMLAOP.Performance.perform(..))"/><!--将一个普通bean定义为切面类,ref属性指向POJO切面类--><aop:aspect ref="audience2"><!--method:切面类中通知方法,pointcut:切入点(使用AspectJ切点表达式语法所定义的切点。)--><aop:around method="watchPerformance" pointcut-ref="performance-cut"></aop:around></aop:aspect></aop:config>
</beans>

测试类:

package com.mzj.springframework.aop.xmlaop;import com.mzj.springframework.aop._02_XMLAOP.Performance;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;/*** @Auther: mazhongjia* @Date: 2020/3/25 13:04* @Version: 1.0*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath*:com/mzj/springframework/aop/_02_XMLAOP/around/XMLAOP2.xml"})
public class AroundTest {@Autowiredprivate Performance performance;@Testpublic void play() {performance.perform();}
}

执行结果:

表演之前.....
PerformanceImpl....
表演成功后.....

3-3)为通知传递参数

业务bean接口

package com.mzj.springframework.aop._02_XMLAOP.adviceArgs;public interface SingASong {void sing(String SongName, String SongContext);
}

业务bean实现

package com.mzj.springframework.aop._02_XMLAOP.adviceArgs;public class SingASongImpl implements SingASong {@Overridepublic void sing(String songName, String songContext) {System.out.println("SingASong " + songName + " : " + songContext);}
}

切面类(普通POJO,无任何spring注解)

package com.mzj.springframework.aop._02_XMLAOP.adviceArgs;import java.util.ArrayList;
import java.util.List;/*** 切面** 从命名切点到 通知方法的参数转移** @Auther: mazhongjia* @Date: 2020/3/23 12:26* @Version: 1.0*/
//@Aspect
public class Audience4AroundArgs {private int songCount = 0;private List<String> songList = new ArrayList<>();//    @Pointcut("execution(* com.mzj.springframework.aop._01_SpringDeclarativeAOP.bean.adviceArgs.SingASong.sing(String,String))" + "&& args(songName,songContext)")
//    public void trackPlayed(String songName,String songContext){};//    @Before("trackPlayed(songName,songContext)")public void countTrack(String songName,String songContext){songCount++;songList.add(songName);System.out.println("已经唱了" + songCount + "首歌曲了,他们是:" + songList);}
}

配置文件

<?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:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://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"><!--普通bean--><bean id="audience2" class="com.mzj.springframework.aop._02_XMLAOP.adviceArgs.Audience4AroundArgs"/><!--业务bean--><bean id="song" class="com.mzj.springframework.aop._02_XMLAOP.adviceArgs.SingASongImpl"/><aop:config><!--将一个普通bean声明为一个切面--><aop:aspect ref="audience2"><!--定义切点,通过新增的args(songName,songContext)将参数传递给通知上,其中ongName,songContext这两个变量对应通知方法countTrack的参数名--><aop:pointcut id="trackPlayed"expression="execution(* com.mzj.springframework.aop._02_XMLAOP.adviceArgs.SingASong.sing(String,String)) and args(songName,songContext)"/><!--method:切面类中通知方法,pointcut:切入点(使用AspectJ切点表达式语法所定义的切点。)--><aop:before method="countTrack" pointcut-ref="trackPlayed"></aop:before></aop:aspect></aop:config></beans>

测试类:

package com.mzj.springframework.aop.xmlaop;import com.mzj.springframework.aop._02_XMLAOP.adviceArgs.SingASong;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;/*** @Auther: mazhongjia* @Date: 2020/3/25 13:04* @Version: 1.0*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath*:com/mzj/springframework/aop/_02_XMLAOP/adviceArgs/XMLAOP2.xml"})
public class AdviceTest {@Autowiredprivate SingASong singASong;@org.junit.Testpublic void songTest() {String song1 = "let it go...let it go...";String song2 = "我爱你中国....我爱你中国....";singASong.sing("Let It Go",song1);singASong.sing("我爱你中国",song2);}
}

运行结果:

已经唱了1首歌曲了,他们是:[Let It Go]
SingASong Let It Go : let it go...let it go...
已经唱了2首歌曲了,他们是:[Let It Go, 我爱你中国]
SingASong 我爱你中国 : 我爱你中国....我爱你中国....

3-4)通过切面引入新的功能

使用Spring aop命名空间中的<aop:declare-parents>元素,可以通过XML配置方式,实现@DeclareParents注解(AspectJ的注解)的功能。
代码示例:
业务bean接口
package com.mzj.springframework.aop._02_XMLAOP.NewFeature;/*** 目标bean接口*/
public interface SingASong {void sing(String SongName, String SongContext);
}

业务bean实现:

package com.mzj.springframework.aop._02_XMLAOP.NewFeature;/*** 目标bean实现*/
public class SingASongImpl implements SingASong {@Overridepublic void sing(String songName, String songContext) {System.out.println("SingASong " + songName + " : " + songContext);}
}

新功能接口:

package com.mzj.springframework.aop._02_XMLAOP.NewFeature;/*** 增加的新功能接口** @Auther: mazhongjia* @Date: 2020/3/24 17:55* @Version: 1.0*/
public interface NewFeature {void newFeature();
}

新功能实现:

package com.mzj.springframework.aop._02_XMLAOP.NewFeature;/*** 增加的新功能实现** @Auther: mazhongjia* @Date: 2020/3/24 17:55* @Version: 1.0*/
public class NewFeatureImpl implements NewFeature {@Overridepublic void newFeature() {System.out.println("增加新功能。。。");}
}

spring配置文件:

<?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:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://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"><!--业务bean--><bean id="song" class="com.mzj.springframework.aop._02_XMLAOP.NewFeature.SingASongImpl"/><!--准备增加的新功能bean-->
<!--    <bean id="newFeature" class="com.mzj.springframework.aop._02_XMLAOP.NewFeature.NewFeatureImpl"/>--><aop:config><!--将一个普通bean声明为一个切面--><aop:aspect><!--types-matching:准备增加新功能的业务bean,其中+代表配置bean的子类--><!--implement-interface:新功能接口--><!--default-impl:新功能实现,也可以使用delegate-ref指定新功能实现类bean的id,使用default-impl来直接标识委托和间接使用delegate-ref的 区别在于后者是Spring bean,它本身可以被注入、通知或使用其他的 Spring配置。而使用default-impl不需要配置新功能实现类为一个bean--><aop:declare-parents types-matching="com.mzj.springframework.aop._02_XMLAOP.NewFeature.SingASong+" implement-interface="com.mzj.springframework.aop._02_XMLAOP.NewFeature.NewFeature" default-impl="com.mzj.springframework.aop._02_XMLAOP.NewFeature.NewFeatureImpl"/></aop:aspect></aop:config></beans>

测试代码:

package com.mzj.springframework.aop.xmlaop;import com.mzj.springframework.aop._02_XMLAOP.NewFeature.NewFeature;
import com.mzj.springframework.aop._02_XMLAOP.NewFeature.SingASong;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;/*** @Auther: mazhongjia* @Date: 2020/3/25 13:04* @Version: 1.0*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath*:com/mzj/springframework/aop/_02_XMLAOP/NewFeature/XMLAOP2.xml"})
public class NewFutureTest {@Autowiredprivate SingASong singASong;@org.junit.Testpublic void songTest() {String song1 = "let it go...let it go...";String song2 = "我爱你中国....我爱你中国....";singASong.sing("Let It Go",song1);singASong.sing("我爱你中国",song2);((NewFeature)singASong).newFeature();}
}

运行结果:

SingASong Let It Go : let it go...let it go...
SingASong 我爱你中国 : 我爱你中国....我爱你中国....
增加新功能。。。

六、AspectJ AOP

1、说明

1、AspectJ AOP功能比SpringAOP强大:
  • 切点支持:构造函数

    • Spring基于代理的AOP无法把通知应用于对象的创建过程
  • 切点支持:成员变量

​​​​​​​2、AspectJ AOP通常来说不需要spring,但是AspectJ AOP的实现过程可能需要借助一些对象提供的功能,这些对象可以

  • 在AspectJ AOP切面中自行new
  • 由Spring IOC进行管理(将这些对象装配到AspectJ AOP切面中)(推荐)

03.spring framework的AOP相关推荐

  1. 使用ProxyFactoryBean创建AOP代理 - Spring Framework reference 2.0.5 参考手册中文版

    http://doc.javanb.com/spring-framework-reference-zh-2-0-5/ch07s05.html 7.5. 使用ProxyFactoryBean创建AOP代 ...

  2. Spring Framework源码使用 spring-aspects AOP遇到的问题

    在编译好的Spring Framework使用Aspects AOP 继续上一篇,编译Spring Framework5.1源码文章.现在要使用Aspects AOP也可能会遇到一些问题,现在这篇文章 ...

  3. 【高危漏洞通告】Spring Framework 远程代码执行 (CVE-2022-22965)

    [高危漏洞通告]Spring Framework 远程代码执行 (CVE-2022-22965)漏洞通告    一. 漏洞情况 Spring 框架(Framework)是一个开源的轻量级 J2EE 应 ...

  4. 手动创建Spring项目 Spring framework

    之前学习框架一直是看的视频教程,并且在都配套有项目源码,跟着视频敲代码总是很简单,现在想深入了解,自己从官网下载文件手动搭建,就遇到了很多问题记载如下. 首先熟悉一下spring的官方网站:http: ...

  5. 一文读懂Spring中的AOP机制

    一.前言 这一篇我们来说一下 Spring 中的 AOP 机制,为啥说完注解的原理然后又要说 AOP 机制呢? 1.标记日志打印的自定义注解 @Target({ElementType.METHOD}) ...

  6. spring原理案例-基本项目搭建 01 spring framework 下载 官网下载spring jar包

    下载spring http://spring.io/ 最重要是在特征下面的这段话,需要注意: All avaible features and modules are described in the ...

  7. 紧随Java 16,Spring Framework 5.3.5 发布:涵盖JDK 16的支持!

    昨天小编刚给大家介绍过最新的Spring Boot 2.4.4.Spring Cloud 2020.0.2 发布内容,还没看过的小伙伴点这里:Spring Boot 2.4.4.Spring Clou ...

  8. Spring Framework 5.2.5 发布,增加对 Java 14 的支持

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 来源 | https://www.oschina.net ...

  9. Spring Framework(框架)整体架构

    原文链接:https://blog.csdn.net/wd2014610/article/details/80061808 Spring 在这个Spring框架大行其道的软件开发世界里,尚有很多工程师 ...

最新文章

  1. js对html进行转义和反转义的操作
  2. 31 天重构学习笔记5. 提升字段
  3. 上海肯特选用Ultimus 提升企业管理
  4. 一步步实现:JPA的基本增删改查CRUD(jpa基于hibernate)
  5. matlab simulink_MATLAB之Simulink(二)利用switch模块将正弦信号变为方波信号
  6. 【Python 必会技巧】获取字典中(多个)最大值(value)的键(key)
  7. leetcode108 将有序数组转换为二叉搜索树
  8. linux utmp结构体,Linux C编程如何使用联机帮助来解决编程问题?
  9. ctr 平滑_CTR预估中的贝叶斯平滑方法及其代码实现
  10. 官方版.NET SDK连线更新(2011/01/19)
  11. python删除列表空元素_Python 如何删除列表中的空值
  12. django python3 异步_详解配置Django的Celery异步之路踩坑
  13. cher怎么翻译中文_中文翻译法语收费标准是怎么定的
  14. python写刷课脚本_python opencv 知到 刷课 脚本
  15. Casbin荣获2021年度“科创中国”开源创新榜优秀开源产品
  16. docx4j文档差异比较
  17. ios python3.0编程软件_ios编程软件-7款学习Python编程的iPhone/iPad应用
  18. java 148. 排序链表
  19. passenger多ruby版本共存部署
  20. Android初学之十二:Broadcast

热门文章

  1. PMP备考大全:经典题库(8月第4周)
  2. 基于WIFI-Pumpkin的流氓AP的实现
  3. 应用宝 android 平板,应用宝HD2.0个性化推荐最优安卓平板软件
  4. 计算机网络(3)——三种常见的数据交换技术
  5. vue3中 v-md-editor 编辑器的基本使用分享
  6. java单例模式(Holder模式美滋滋)
  7. 处理微信里接龙名单,看看谁没有参加接龙
  8. 六相PMSM电驱平台及电机相序、参数测试流程
  9. 数码显示实验报告C语言,数码管动态显示实验报告
  10. List of regional organizations by population