一、AOP——另一种编程思想
1.1、 AOP是什么?
- AOP (Aspect Orient Programming),直译过来就是 面向切面编程。AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面。
- AOP的作用就是将分散在各个业务逻辑代码中的相同代码,通过横向切割的方式抽取到一个独立的模块中,让业务逻辑类依然保存最初的单纯。
- 这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。AOP技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
- 通过Java动态代理的基于接口的方式进行织入,这时候代理生成的是一个接口对象,将这个接口对象强制转换为实现该接口的一个类,会抛出类型转换异常。
- 使用 cglib 的动态代理方式。这种方式的缺点是拓展类的方法被final修饰时,无法进行织入。
- Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP代理可以直接使用容器中的其它bean实例作为目标,这种关系可由IOC容器的依赖注入提供。
从该图可以很形象地看出,所谓切面,相当于应用对象间的横切点,我们可以将其单独抽象为单独的模块。
1.2、为什么需要 AOP
如果在多个模块间有某段重复的代码,在传统的面向过程编程中,我们也会将这段代码,抽象成一个方法,然后在需要的地方分别调用这个方法,这样当这段代码需要修改时,我们只需要改变这个方法就可以了。然而需求总是变化的,有一天,新增了一个需求,需要再做出修改,我们需要再抽象出一个方法,然后再在需要的地方分别调用这个方法,又或者我们不需要这个方法了,我们还是得删除掉每一处调用该方法的地方。实际上涉及到多个地方具有相同的修改的问题我们都可以通过 AOP 来解决。
1.3、Spring AOP是什么?
Aspect Oriented Programming:面向切面编程,如果多个类中出现重复的代码,就应该考虑定义一个共同的抽象类,将这些共同的代码提取到抽象类中,这种情况,我们称为纵向抽取。PerformanceMonitor TransactionManager AOP就是希望将这些分散在各个业务逻辑代码中的相同代码,通过横向切割的方式抽取到一个独立的模块中,让业务逻辑类依然保存最初的单纯。抽取出来简单,难点就是如何将这些独立的逻辑融合到业务逻辑中,完成跟原来一样的业务逻辑,这就是AOP解决的主要问题。
二、AOP术语
- 连接点(Joinpoint):程序执行的某个特定位置,如某个方法调用前,调用后,方法抛出异常后,这些代码中的特定点称为连接点。简单来说,就是在哪加入你的逻辑增强。连接点表示具体要拦截的方法,上面切点是定义一个范围,而连接点是具体到某个方法。连接点表示应用执行过程中能够插入切面的一个点,这个点可以是方法的调用、异常的抛出。在 Spring AOP 中,连接点总是方法的调用。
- 切点(PointCut): 可以插入增强处理的连接点。每个程序的连接点有多个,如何定位到某个感兴趣的连接点,就需要通过切点来定位。比如,连接点–数据库的记录,切点–查询条件
切点用于来限定Spring-AOP启动的范围,通常我们采用表达式的方式来设置,所以关键词是范围 - 通知(Advice): AOP 框架中的增强处理。通知描述了切面何时执行以及如何执行增强处理。通知是织入到目标类连接点上的一段程序代码。在Spring中,像BeforeAdvice等还带有方位信息
通知是直译过来的结果,我个人感觉叫做“业务增强”更合适 对照代码就是拦截器定义的相关方法,通知分为如下几种:- 前置通知(before):在执行业务代码前做些操作,比如获取连接对象
- 后置通知(after):在执行业务代码后做些操作,无论是否发生异常,它都会执行,比如关闭连接对象
- 异常通知(afterThrowing):在执行业务代码后出现异常,需要做的操作,比如回滚事务
- 返回通知(afterReturning):在执行业务代码后无异常,会执行的操作
- 环绕通知(around):这个目前跟我们谈论的事务没有对应的操作,所以暂时不谈
- 目标对象(Target) :需要被加强的业务对象
- 引入(Introduction):引入允许我们向现有的类添加新的方法或者属性。
- 织入(Weaving): 织入就是将增强处理添加到对目标类具体连接点上(目标对象中),并创建一个被增强的对象,这个过程就是织入。织入是一个形象的说法,具体来说,就是生成代理对象并将切面内容融入到业务流程的过程。织入是一个形象的说法,具体来说,就是生成代理对象并将切面内容融入到业务流程的过程。
- 切面(Aspect) :切面由切点和通知组成,它既包括了横切逻辑的定义,也包括了连接点的定义,SpringAOP就是将切面所定义的横切逻辑织入到切面所制定的连接点中。
比如上文讨论的数据库事务,这个数据库事务代码贯穿了我们的整个代码,我们就可以这个叫做切面。 SpringAOP将切面定义的内容织入到我们的代码中,从而实现前后的控制逻辑。 比如我们常写的拦截器Interceptor,这就是一个切面类。
三、初步认识 Spring AOP
3.1、Spring AOP 的特点
- AOP编程可不是Spring独有的,Spring只是支持AOP编程的框架之一,这一点非常重要,切勿搞反了关系;
- AOP分两类,一类可以对方法的参数进行拦截,一类是对方法进行拦截,SpringAOP属于后者,所以Spring的AOP是属于方法级的;
- 不同的 AOP 框架支持的连接点也有所区别,例如,AspectJ 和 JBoss,除了支持方法切点,它们还支持字段和构造器的连接点。
- Spring 中的 AOP 是通过动态代理实现的,Spring AOP 不能拦截对对象字段的修改,也不支持构造器连接点,我们无法在 Bean 创建时应用通知。
3.2 Spring AOP 的简单例子
这个例子是基于gradle创建的,首先 build.gradle 文件添加依赖:
dependencies {
compile 'org.springframework:spring-context:5.0.6.RELEASE'
}
首先创建一个接口 IBuy.java
package com.sharpcj.aopdemo.test1;
public interface IBuy {
String buy();
}
Boy 和 Gril 两个类分别实现了这个接口:
Boy.java
package com.sharpcj.aopdemo.test1;
import org.springframework.stereotype.Component;
@Component
public class Boy implements IBuy {
@Override
public String buy() {
System.out.println("男孩买了一个游戏机");
return "游戏机";
}
}
Girl.java
package com.sharpcj.aopdemo.test1;
import org.springframework.stereotype.Component;
@Component
public class Girl implements IBuy {
@Override
public String buy() {
System.out.println("女孩买了一件漂亮的衣服");
return "衣服";
}
}
配置类:AppConfig.java
package com.sharpcj.aopdemo;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackageClasses = {com.sharpcj.aopdemo.test1.IBuy.class})
public class AppConfig {
}
测试类: AppTest.java
package com.sharpcj.aopdemo;
import com.sharpcj.aopdemo.test1.Boy;
import com.sharpcj.aopdemo.test1.Girl;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class AppTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
Boy boy = context.getBean("boy",Boy.class);
Girl girl = (Girl) context.getBean("girl");
boy.buy();
girl.buy();
}
}
运行结果:
这里运用SpringIOC里的自动部署。现在需求改变了,我们需要在男孩和女孩的 buy 方法之前,需要打印出“男孩女孩都买了自己喜欢的东西”。用 Spring AOP 来实现这个需求只需下面几个步骤:
1、 既然用到 Spring AOP, 首先在 build.gralde 文件中引入相关依赖:
dependencies {
compile 'org.springframework:spring-context:5.0.6.RELEASE'
compile 'org.springframework:spring-aspects:5.0.6.RELEASE'
}
2、 定义一个切面类:BuyAspectJ.java
package com.sharpcj.aopdemo.test1;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class BuyAspectJ {
@Before("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")
public void haha(){
System.out.println("男孩女孩都买自己喜欢的东西");
}
}
这个类,我们使用了注解 @Component 表明它将作为一个Spring Bean 被装配,使用注解 @Aspect 表示它是一个切面。
类中只有一个方法 haha 我们使用 @Before 这个注解,表示他将在方法执行之前执行。关于这个注解后文再作解释。
参数(“execution(* com.sharpcj.aopdemo.test1.IBuy.buy(…))”) 声明了切点,表明在该切面的切点是com.sharpcj.aopdemo.test1.Ibuy这个接口中的buy方法。至于为什么这么写,下文再解释。
3、 在配置文件中启用AOP切面功能
配置类:AppConfig.java
package com.sharpcj.aopdemo;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan(basePackageClasses = {com.sharpcj.aopdemo.test1.IBuy.class})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig {
}
我们在配置文件类增加了@EnableAspectJAutoProxy注解,启用了 AOP 功能,参数proxyTargetClass的值设为了 true 。默认值是 false,两者的区别下文再解释。
OK,下面只需测试代码,运行结果如下:
我们看到,结果与我们需求一致,我们并没有修改 Boy 和 Girl 类的 Buy 方法,也没有修改测试类的代码,几乎是完全无侵入式地实现了需求。这就是 AOP 的“神奇”之处。
四、通过注解配置 Spring AOP
4.1、通过注解声明切点指示器
Spring AOP 所支持的 AspectJ 切点指示器
在spring中尝试使用AspectJ其他指示器时,将会抛出IllegalArgumentException异常。
当我们查看上面展示的这些spring支持的指示器时,注意只有execution指示器是唯一的执行匹配,而其他的指示器都是用于限制匹配的。这说明execution指示器是我们在编写切点定义时最主要使用的指示器,在此基础上,我们使用其他指示器来限制所匹配的切点。
下图的切点表达式表示当Instrument的play方法执行时会触发通知。
我们使用execution指示器选择Instrument的play方法,方法表达式以 * 号开始,表示我们不关心方法的返回值类型。然后我们指定了全限定类名和方法名。对于方法参数列表,我们使用 … 表示切点选择任意的play方法,无论该方法的入参是什么。
举例:
限定该切点仅匹配的包是 com.sharpcj.aopdemo.test1,可以使用
execution(* com.sharpcj.aopdemo.test1.IBuy.buy(…)) &&within(com.sharpcj.aopdemo.test1.*)
在切点中选择 bean,可以使用
execution(* com.sharpcj.aopdemo.test1.IBuy.buy(…)) && bean(girl)
切点表达式:
- 任意公共方法的执行:execution(public * *(…))
- 任何一个以“set”开始的方法的执行:execution(* set*(…))
- AccountService 接口的任意方法的执行:execution(* com.xyz.service.AccountService.*(…))
- 定义在service包里的任意方法的执行: execution(* com.xyz.service..(…))
- 定义在service包和所有子包里的任意类的任意方法的执行:execution(* com.xyz.service….(…))
第一个表示匹配任意的方法返回值,…(两个点)表示零个或多个,第一个…表示service包及其子包,第二个表示所有类,
第三个*表示所有方法,第二个…表示方法的任意参数个数
- 定义在pointcutexp包和所有子包里的JoinPointObjP2类的任意方法的执行:execution(com.test.spring.aop.pointcutexp…JoinPointObjP2.(…))")
- pointcutexp包里的任意类: within(com.test.spring.aop.pointcutexp.*)
- pointcutexp包和所有子包里的任意类:within(com.test.spring.aop.pointcutexp…*)
- 实现了Intf接口的所有类,如果Intf不是接口,限定Intf单个类:this(com.test.spring.aop.pointcutexp.Intf)
- 当一个实现了接口的类被AOP的时候,用getBean方法必须cast为接口类型,不能为该类的类型
- 带有@Transactional标注的所有类的任意方法: @within(org.springframework.transaction.annotation.Transactional) @target(org.springframework.transaction.annotation.Transactional)
- 带有@Transactional标注的任意方法:
@annotation(org.springframework.transaction.annotation.Transactional)
@within和@target针对类的注解,@annotation是针对方法的注解 - 参数带有@Transactional标注的方法:@args(org.springframework.transaction.annotation.Transactional)
- 参数为String类型(运行是决定)的方法: args(String)
修改切面类:BuyAspectJ.java
package com.sharpcj.aopdemo.test1;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class BuyAspectJ {
@Before("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..)) && within(com.sharpcj.aopdemo.test1.*) && bean(girl)")
public void hehe(){
System.out.println("男孩女孩都买自己喜欢的东西");
}
}
此时,切面只会对 Girl.java 这个类生效,执行结果:
细心的你,可能发现了,切面中的方法名,已经被我悄悄地从haha改成了hehe,丝毫没有影响结果,说明方法名没有影响。和 Spring IOC 中用 java 配置文件装配 Bean 时,用@Bean 注解修饰的方法名一样,没有影响。
4.2、通过注解声明 5 种通知类型
Spring AOP 中有 5 中通知类型,分别如下:
修改切面类:BuyAspectJ.java
package com.sharpcj.aopdemo.test1;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class BuyAspectJ {
@Before("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")
public void hehe() {
System.out.println("before ...");
}
@After("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")
public void haha() {
System.out.println("After ...");
}
@AfterReturning("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")
public void xixi() {
System.out.println("AfterReturning ...");
}
@Around("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")
public void xxx(ProceedingJoinPoint pj) {
try {
System.out.println("Around aaa ...");
pj.proceed();
System.out.println("Around bbb ...");
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}
为了方便看效果,我们测试类中,只要 Boy 类:
package com.sharpcj.aopdemo;
import com.sharpcj.aopdemo.test1.Boy;
import com.sharpcj.aopdemo.test1.Girl;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class AppTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
Boy boy = context.getBean("boy",Boy.class);
Girl girl = (Girl) context.getBean("girl");
boy.buy();
// girl.buy();
}
}
执行结果如下:
结果显而易见。指的注意的是 @Around 修饰的环绕通知类型,是将整个目标方法封装起来了,在使用时,我们传入了 ProceedingJoinPoint 类型的参数,这个对象是必须要有的,并且需要调用 ProceedingJoinPoint 的 proceed() 方法。 如果没有调用 该方法,执行结果为 :
Around aaa ...
Around bbb ...
After ...
AfterReturning ...
4.3、通过注解声明切点表达式
如你看到的,上面我们写的多个通知使用了相同的切点表达式,对于像这样频繁出现的相同的表达式,我们可以使用 @Pointcut注解声明切点表达式,然后使用表达式,修改代码如下:
修改切面类:BuyAspectJ.java
package com.sharpcj.aopdemo.test1;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class BuyAspectJ {
@Pointcut("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")
public void point(){}
@Before("point()")
public void hehe() {
System.out.println("before ...");
}
@After("point()")
public void haha() {
System.out.println("After ...");
}
@AfterReturning("point()")
public void xixi() {
System.out.println("AfterReturning ...");
}
@Around("point()")
public void xxx(ProceedingJoinPoint pj) {
try {
System.out.println("Around aaa ...");
pj.proceed();
System.out.println("Around bbb ...");
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}
程序运行结果没有变化。
这里,我们使用
@Pointcut("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")
public void point(){}
声明了一个切点表达式,该方法 point 的内容并不重要,方法名也不重要,实际上它只是作为一个标识,供通知使用。
4.4、通过注解处理通知中的参数
上面的例子,我们要进行增强处理的目标方法没有参数,下面我们来说说有参数的情况,并且在增强处理中使用该参数。
下面我们给接口增加一个参数,表示购买所花的金钱。通过AOP 增强处理,如果女孩买衣服超过了 68 元,就可以赠送一双袜子。
更改代码如下:
IBuy.java
package com.sharpcj.aopdemo.test1;
public interface IBuy {
String buy(double price);
}
Girl.java
package com.sharpcj.aopdemo.test1;
import org.springframework.stereotype.Component;
@Component
public class Girl implements IBuy {
@Override
public String buy(double price) {
System.out.println(String.format("女孩花了%s元买了一件漂亮的衣服", price));
return "衣服";
}
}
Boy.java
package com.sharpcj.aopdemo.test1;
import org.springframework.stereotype.Component;
@Component
public class Boy implements IBuy {
@Override
public String buy(double price) {
System.out.println(String.format("男孩花了%s元买了一个游戏机", price));
return "游戏机";
}
}
再看切面类 BuyAspectJ.java,我们将之前的通知都注释掉。用一个环绕通知来实现这个功能:
package com.sharpcj.aopdemo.test1;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class BuyAspectJ {
/*
@Pointcut("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")
public void point(){}
@Before("point()")
public void hehe() {
System.out.println("before ...");
}
@After("point()")
public void haha() {
System.out.println("After ...");
}
@AfterReturning("point()")
public void xixi() {
System.out.println("AfterReturning ...");
}
@Around("point()")
public void xxx(ProceedingJoinPoint pj) {
try {
System.out.println("Around aaa ...");
pj.proceed();
System.out.println("Around bbb ...");
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
*/
@Pointcut("execution(String com.sharpcj.aopdemo.test1.IBuy.buy(double)) && args(price) && bean(girl)")
public void gif(double price) {
}
@Around("gif(price)")
public String hehe(ProceedingJoinPoint pj, double price){
try {
pj.proceed();
if (price > 68) {
System.out.println("女孩买衣服超过了68元,赠送一双袜子");
return "衣服和袜子";
}
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return "衣服";
}
}
前文提到,当不关心方法返回值的时候,我们在编写切点指示器的时候使用了 * , 当不关心方法参数的时候,我们使用了 …。现在如果我们需要传入参数,并且有返回值的时候,则需要使用对应的类型。在编写通知的时候,我们也需要声明对应的返回值类型和参数类型。
测试类:AppTest.java
package com.sharpcj.aopdemo;
import com.sharpcj.aopdemo.test1.Boy;
import com.sharpcj.aopdemo.test1.Girl;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class AppTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
Boy boy = context.getBean("boy",Boy.class);
Girl girl = (Girl) context.getBean("girl");
String boyBought = boy.buy(35);
String girlBought = girl.buy(99.8);
System.out.println("男孩买到了:" + boyBought);
System.out.println("女孩买到了:" + girlBought);
}
}
测试结果:
可以看到,我们成功通过 AOP 实现了需求,并将结果打印了出来。
4.5、通过注解配置织入的方式
前面还有一个遗留问题,在配置文件中,我们用注解 @EnableAspectJAutoProxy() 启用Spring AOP 的时候,我们给参数 proxyTargetClass 赋值为 true,如果我们不写参数,默认为 false。这个时候运行程序,程序抛出异常
这是一个强制类型转换异常。为什么会抛出这个异常呢?或许已经能够想到,这跟Spring AOP 动态代理的机制有关,这个 proxyTargetClass 参数决定了代理的机制。
1. 当proxyTargetClass 为 false 时,通过jdk的基于接口的方式进行织入,这时候代理生成的是一个接口对象,将这个接口对象强制转换为实现该接口的一个类,自然就抛出了上述类型转换异常。
2. 当proxyTargetClass 为 true 时,则会使用 cglib 的动态代理方式。这种方式的缺点是拓展类的方法被final修饰时,无法进行织入。
测试一下,我们将 proxyTargetClass 参数设为 true,同时将 Girl.java 的 Buy 方法用 final 修饰:
配置类:AppConfig.java
package com.sharpcj.aopdemo;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan(basePackageClasses = {com.sharpcj.aopdemo.test1.IBuy.class})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig {
}
Girl.java
package com.sharpcj.aopdemo.test1;
import org.springframework.stereotype.Component;
@Component
public class Girl implements IBuy {
@Override
public final String buy(double price) {
System.out.println(String.format("女孩花了%s元买了一件漂亮的衣服", price));
return "衣服";
}
}
此时运行结果:
可以看到,我们的切面并没有织入生效。
五、通过 XML 配置文件声明切面
前面的示例中,我们已经展示了如何通过注解配置去声明切面,下面我们看看如何在 XML 文件中声明切面。下面先列出 XML 中声明 AOP 的常用元素:
我们依然可以使用<aop:aspectj-autoproxy>
元素,他能够自动代理AspectJ注解的通知类。
5.1、XML 配置文件中切点指示器
在XML配置文件中,切点指示器表达式与通过注解配置的写法基本一致,区别前面有提到,即XML文件中需要使用 “and”、“or”、“not”来表示 “且”、“或”、“非”的关系。
5.2、XML 文件配置 AOP 实例
下面我们不使用任何注解改造上面的例子:
切面类:BuyAspectJ.java
package com.sharpcj.aopdemo.test2;
import org.aspectj.lang.ProceedingJoinPoint;
public class BuyAspectJ {
public void hehe() {
System.out.println("before ...");
}
public void haha() {
System.out.println("After ...");
}
public void xixi() {
System.out.println("AfterReturning ...");
}
public void xxx(ProceedingJoinPoint pj) {
try {
System.out.println("Around aaa ...");
pj.proceed();
System.out.println("Around bbb ...");
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}
在 Resource 目录下新建一个配置文件 aopdemo.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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="boy" class="com.sharpcj.aopdemo.test2.Boy"></bean>
<bean id="girl" class="com.sharpcj.aopdemo.test2.Girl"></bean>
<bean id="buyAspectJ" class="com.sharpcj.aopdemo.test2.BuyAspectJ"></bean>
<aop:config proxy-target-class="true">
<aop:aspect id="qiemian" ref="buyAspectJ">
<aop:before pointcut="execution(* com.sharpcj.aopdemo.test2.IBuy.buy(..))" method="hehe"/>
<aop:after pointcut="execution(* com.sharpcj.aopdemo.test2.IBuy.buy(..))" method="haha"/>
<aop:after-returning pointcut="execution(* com.sharpcj.aopdemo.test2.IBuy.buy(..))" method="xixi"/>
<aop:around pointcut="execution(* com.sharpcj.aopdemo.test2.IBuy.buy(..))" method="xxx"/>
</aop:aspect>
</aop:config>
</beans>
这里分别定义了一个切面,里面包含四种类型的通知。
测试文件中,使用
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("aopdemo.xml");
来获取 ApplicationContext,其它代码不变。
5.3、XML 文件配置声明切点
对于频繁重复使用的切点表达式,我们也可以声明成切点。
配置文件如下:aopdemo.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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="boy" class="com.sharpcj.aopdemo.test2.Boy"></bean>
<bean id="girl" class="com.sharpcj.aopdemo.test2.Girl"></bean>
<bean id="buyAspectJ" class="com.sharpcj.aopdemo.test2.BuyAspectJ"></bean>
<aop:config proxy-target-class="true">
<aop:pointcut id="apoint" expression="execution(* com.sharpcj.aopdemo.test2.IBuy.buy(..))"/>
<aop:aspect id="qiemian" ref="buyAspectJ">
<aop:before pointcut-ref="apoint" method="hehe"/>
<aop:after pointcut-ref="apoint" method="haha"/>
<aop:after-returning pointcut-ref="apoint" method="xixi"/>
<aop:around pointcut-ref="apoint" method="xxx"/>
</aop:aspect>
</aop:config>
</beans>
5.4、XML文件配置为通知传递参数
切面类:BuyAspectJ.java
package com.sharpcj.aopdemo.test2;
import org.aspectj.lang.ProceedingJoinPoint;
public class BuyAspectJ {
public String hehe(ProceedingJoinPoint pj, double price){
try {
pj.proceed();
if (price > 68) {
System.out.println("女孩买衣服超过了68元,赠送一双袜子");
return "衣服和袜子";
}
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return "衣服";
}
}
配置文件:aopdemo.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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="boy" class="com.sharpcj.aopdemo.test2.Boy"></bean>
<bean id="girl" class="com.sharpcj.aopdemo.test2.Girl"></bean>
<bean id="buyAspectJ" class="com.sharpcj.aopdemo.test2.BuyAspectJ"></bean>
<aop:config proxy-target-class="true">
<aop:pointcut id="apoint" expression="execution(String com.sharpcj.aopdemo.test2.IBuy.buy(double)) and args(price) and bean(girl)"/>
<aop:aspect id="qiemian" ref="buyAspectJ">
<aop:around pointcut-ref="apoint" method="hehe"/>
</aop:aspect>
</aop:config>
</beans>
5.5、Xml 文件配置织入的方式
同注解配置类似
CGlib 代理方式:
<aop:config proxy-target-class="true"> </aop:config>
JDK 代理方式:
<aop:config proxy-target-class="false"> </aop:config>
六、总结
本文简单记录了 AOP 的编程思想,然后介绍了 Spring 中 AOP 的相关概念,以及通过注解方式和XML配置文件两种方式使用 Spring AOP进行编程。 相比于 AspectJ 的面向切面编程,Spring AOP 也有一些局限性,但是已经可以解决开发中的绝大多数问题了,如果确实遇到了 Spring AOP 解决不了的场景,我们依然可以在 Spring 中使用 AspectJ 来解决。