什么是 AOP
AOP (Aspect Orient Programming),面向切面编程,是一种编程思想,是面向对象编程(OOP)的一种补充。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面。
从《Spring实战(第4版)》图书中扒了一张图:
相当于应用对象间的横切点,我们可以将其单独抽象为单独的模块
为什么需要 AOP
想象一个场景,开发中有多个模块间有某段重复的代码,在传统的程编程中,我们会将这段代码抽象成一个方法。然而随着需求的修改,我们不需要这个方法了,就要删除所有调用这个方法的地方,实际上涉及到多个地方具有相同的修改的问题我们都可以通过 AOP 来解决。
AOP 实现分类
AOP 要达到的效果是,保证开发者不修改源代码的前提下,去为系统中的业务组件添加某种通用功能。
AOP 其实就是代理模式的典型应用。
按照 AOP 框架修改源代码的时机,可以将其分为两类:
静态 AOP 实现, AOP 框架在编译阶段对程序源代码进行修改,生成了静态的 AOP 代理类(生成的 *.class 文件已经被改掉了,需要使用特定的编译器),比如 AspectJ。
动态 AOP 实现, AOP 框架在运行阶段对动态生成代理对象(在内存中以 JDK 动态代理,或 CGlib 动态地生成 AOP 代理类),SpringAOP。
下面给出常用 AOP 实现比
AOP 术语
AOP 领域中的特性术语:
通知(Advice):通知包含两个部分,通知内容和以及如何执行增强处理。在切面的某个特定连接点上执行的动作
通知类型:
Before :前置通知,在连接点方法前调用
After :后置通知,在连接点方法后调用
AfterReturning:返回通知,在连接点方法执行并正常返回后调用,要求连接点方法在执行过程中没有发生异常
AfterThrowing:异常通知,当连接点方法异常时调用
Around:环绕通知,它将覆盖原有方法,但是允许你通过反射调用原有方法,后面会讲切面(Aspect):切面包括连接点,切点,通知的一个载体。
切入点(Pointcut):可以插入增强处理的连接点。
切点匹配表达式:
execution:可以定义到方法的的最小粒度是参数的返回类型,修饰符,包名,类名,方法名,Spring AOP主要也是使用这个匹配表达式。
within:只能定义到类
this:当前生成的代理对象的类型匹配
target:目标对象类型匹配
args:只针对参数
例如定义切入点表达式 execution(* com.sample.service.impl...(..))
execution()是最常用的切点函数,其语法如下所示:
整个表达式可以分为五个部分: **
1、execution(): 表达式主体。
2、第一个号:表示返回类型,号表示所有的类型。
3、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service.impl包、子孙包下所有类的方法。
4、第二个号:表示类名,号表示所有的类。
5、(..):最后这个星号表示方法名,号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。
execution(public * (..)) 切入点为执行所有的public方式时
execution( set(..)) 切入点执行所有的set开始的方法时
execution( com.xyz.service.Account.(..)) 切入点执行Account类的所有方法时
execution( com.xyz.service..(..))切入点执行com.xyz.service包下的所有方法时
execution(* com.xyz.service...(..)) 切入点执行com.xyz.service包以及其子包的所有的方法时
连接点(Joinpoint):连接点表示应用执行过程中能够插入切面的一个点,这个点可以是方法的调用、异常的抛出。
引入(Intruduction): 在不修改类代码的前提下,为类添加新的方法和属性
目标对象(Target Object): 被一个或者多个切面所通知的对象
Aop代理(AOP Proxy): AOP框架创建的对象,用来实现切面契约(aspect contract)(包括方法执行等)
-
织入(Weaving):将增强处理添加到目标对象中,并创建一个被增强的对象,这个过程就是织入
AOP使用场景:
Authentication 权限
Caching 缓存
缓存优化 (第一次调用查询数据库,将查询结果放入内存对象, 第二次调用, 直接从内存对象返回,不需要查询数据库 )
Context passing 内容传递
Error handling 错误处理
Lazy loading 懒加载
Debugging 调试
logging, tracing, profiling and monitoring 记录跟踪 优化 校准
Performance optimization 性能优化
Persistence 持久化
Resource pooling 资源池
Synchronization 同步
Transactions 事务管理 (调用方法前开启事务, 调用方法后提交关闭事务 )
监控方法运行时间 (监控性能)
记录日志
初步认识 Spring AOP
3.1 Spring AOP 的特点
AOP 框架有很多种,1.3节中介绍了 AOP 框架的实现方式有可能不同, Spring 中的 AOP 是通过动态代理实现的。不同的 AOP 框架支持的连接点也有所区别,例如,AspectJ 和 JBoss,除了支持方法切点,它们还支持字段和构造器的连接点。而 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切面功能
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方法,无论该方法的入参是什么。
多个匹配之间我们可以使用链接符 &&、||、!来表示 “且”、“或”、“非”的关系。但是在使用 XML 文件配置时,这些符号有特殊的含义,所以我们使用 “and”、“or”、“not”来表示。
举例:
限定该切点仅匹配的包是 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)
修改 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 中通知类型,分别如下:
下面修改切面类:
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();
}
}
在配置文件中启用AOP切面功能
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 的“神奇”之处。
执行结果如下:
结果显而易见。指的注意的是 @Around
修饰的环绕通知类型,是将整个目标方法封装起来了,在使用时,我们传入了 ProceedingJoinPoint
类型的参数,这个对象是必须要有的,并且需要调用 ProceedingJoinPoint
的 proceed()
方法。 如果没有调用 该方法,执行结果为 :
Around aaa ...
Around bbb ...
After ...
AfterReturning ...
可见,如果不调用该对象的 proceed() 方法,表示原目标方法被阻塞调用,当然也有可能你的实际需求就是这样。
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 类,我们将之前的通知都注释掉。用一个环绕通知来实现这个功能:
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(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);
}
}
测试结果:
![](https://upload-images.jianshu.io/upload_images/4055666-0aa49e3c7b505861.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
可以看到,我们成功通过 AOP 实现了需求,并将结果打印了出来。
#####4.5 通过注解配置织入的方式
前面还有一个遗留问题,在配置文件中,我们用注解 @EnableAspectJAutoProxy() 启用Spring AOP 的时候,我们给参数 proxyTargetClass 赋值为 true,如果我们不写参数,默认为 false。这个时候运行程序,程序抛出异常
![](https://upload-images.jianshu.io/upload_images/4055666-306895231745dada.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
这是一个强制类型转换异常。为什么会抛出这个异常呢?或许已经能够想到,这跟Spring AOP 动态代理的机制有关,这个 proxyTargetClass 参数决定了代理的机制。当这个参数为 false 时,
通过jdk的基于接口的方式进行织入,这时候代理生成的是一个接口对象,将这个接口对象强制转换为实现该接口的一个类,自然就抛出了上述类型转换异常。
反之,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 "衣服";
}
}
此时运行结果:
![](https://upload-images.jianshu.io/upload_images/4055666-dd0accefa8961438.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
可以看到,我们的切面并没有织入生效。
#五、通过 XML 配置文件声明切面
前面的示例中,我们已经展示了如何通过注解配置去声明切面,下面我们看看如何在 XML 文件中声明切面。下面先列出 XML 中声明 AOP 的常用元素:
![](https://upload-images.jianshu.io/upload_images/4055666-369750ce5c88a05e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
我们依然可以使用 <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 来解决。
# Spring AOP 最热门面试题及答案
看完 [Spring 核心面试题之后](https://howtodoinjava.com/),让我们来看一下 Spring AOP 面试题,这个你可能会在下一次技术面试的时候遇到。同样的,请尽管提出一些这个帖子相关的新的问题来,我会把它们包含进来,以让更多的读者受益。
# 内容大纲:
* 描述一下Spring AOP?
* 在Spring AOP中关注点(concern)和横切关注点(cross-cutting concern)有什么不同?
* AOP有哪些可用的实现?
* Spring中有哪些不同的通知类型(advice types)?
* Spring AOP 代理是什么?
* 引介(Introduction)是什么?
* 连接点(Joint Point)和切入点(Point Cut)是什么?
* 织入(Weaving)是什么?
#描述一下Spring AOP
Spring AOP(Aspect Oriented Programming,面向切面编程)是OOPs(面向对象编程)的补充,它也提供了模块化。在面向对象编程中,关键的单元是对象,AOP的关键单元是切面,或者说关注点(可以简单地理解为你程序中的独立模块)。一些切面可能有集中的代码,但是有些可能被分散或者混杂在一起,例如日志或者事务。这些分散的切面被称为横切关注点。一个横切关注点是一个可以影响到整个应用的关注点,而且应该被尽量地集中到代码的一个地方,例如事务管理、权限、日志、安全等。
AOP让你可以使用简单可插拔的配置,在实际逻辑执行之前、之后或周围动态添加横切关注点。这让代码在当下和将来都变得易于维护。如果你是使用XML来使用切面的话,要添加或删除关注点,你不用重新编译完整的源代码,而仅仅需要修改配置文件就可以了。
Spring AOP通过以下两种方式来使用。但是最广泛使用的方式是Spring AspectJ 注解风格(Spring AspectJ Annotation Style)
使用AspectJ 注解风格
使用Spring XML 配置风格
问:请说一下SpringAOP的原理。
答:SpringAOP是通过动态代理来实现的。
问:你说的动态代理是JDK的动态代理吗?
答:是的。
问:那被代理的类没有实现接口的情况,这是怎么实现的?
答:...
回来后,我搜索了很多资料、最后总结如下:
(1) SpringAOP是动态代理来实现的。有两种代理方式:JDK动态代理与CGLIB动态代理
(2) JDK动态代理:是通过反射来接收被代理类,要求必须实现一个接口
(3) CGLIB动态代理:当被代理类没有实现一个接口的时候,就会使用CGLIB进行动态代理。CGLIB动态代理通过运行时动态生成被代理类的子类,运用继承的方式来实现动态代理。如果被代理类被final修饰了,那么就不能使用CGLIB进行动态代理了。