首先我们要知道aop分为哪几种代理?
分为两种,一种是jdk代理。它是根据接口来进行开发的。第二种是cglib代理,它是通过继承的关系来进行开发的。
那么AOP底层到底使用那种代理方式?
在spring中,框架会根据目标类是否实现接口来决定采用那种动态代理的方式。(所以我们不用自己考虑)
我们再了解一下AOP的相关术语:
Target(目标对象)︰代理的目标对象(意思就是我们需要增强的对象)
Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类
Joinpoint(连接点)︰所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点(这个意思的是可能成为被增强的方法就是连接点)
Pointcut (切入点)︰所谓切入点是指我们要对哪些Joinpoint进行拦截的定义(又称为切点,切点是指被增强的连接点)
Advice(通知/增强)︰所谓通知是指拦截到Joinpoint之后所要做的事情就是通知(指的是为目标对象增强的方法)
Aspect(切面)︰是切入点和通知(引介)的结合(就是切点和通知的结合)
Weaving(织入)︰是指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入(把通知加入到目标对象的过程就叫做织入)
aop的开发步骤:
1,导入AOP相关坐标
2,创建目标接口和目标类(内部有切点)
3,创建切面类(内部有增强方法)
4,将目标类和切面类的对象创建权交给spring
5,在applicationContext.xml中配置织入关系
6,测试代码
在我看来这种动态代理无非就是把几个方法加入到目标方法中去。
现在下面我们就来进行代码演示:
第一步:导入AOP相关坐标(其实我们在导入spring-context框架中其实已经有了aop的jar包,但是spring框架还是希望我们用第三方jar包,因为第三方jar包可能比它更完善)
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.4</version>
</dependency>
第二步:创建目标接口和目标类(内部有切点)
接口:
public interface TargetInterface {
public void save();
}
实现类:
public class Target implements TargetInterface { @Override public void save() { System.out.println("save running..."); } }
第三步:创建切面类(内部有增强方法)
//切面类
public class MyAspect {
public void before(){
System.out.println("前置增强...");
}
}
第四步:将目标类和切面类的对象创建权交给spring(也就是为这两个类创建Bean对象)
<!-- 目标对象-->
<bean id="target" class="com.outlier.aop.Target"></bean>
<!-- 切面对象-->
<bean id="myAspect" class="com.outlier.aop.MyAspect"></bean>
第五步:在applicationContext.xml中配置织入关系
<!-- 配置织入:告诉spring框架 哪些方法(切点)需要进行哪些增强(前置,后置....)-->
<aop:config>
<!-- 声明切面-->
<aop:aspect ref="myAspect">
<!-- 切面=切点+通知-->
<aop:before method="before" pointcut="execution(public void com.outlier.aop.Target.save())"/>
</aop:aspect>
</aop:config>
解释:第一层标签:<aop:config>表示的是这是一个aop配置。第二层标签:
<aop:aspect ref="myAspect">用来声明切面,ref就是把引用放到这,告诉spring这个就是切面。
第三层标签:表示哪些增强,这一层标签不仅仅有before(前置增强)还要其他一系列的增强。
第三层标签里面的属性:method,用来表示哪个是通知(增强方法)。就拿这个为例子:这里的method=before表示的是切面myAspect里面的before方法为前置增强。这里面的参数都是根据切面的方法来命名的(所以在切面中想怎么命名就怎么命名,但为了阅读起来方便我们就一一对应的来命名)。
属性pointcut表示的是切点(需要被增强的方法)。里面的命名规则为:
1,访问修饰符可以省略
2,返回值类型、包名、类名、方法名可以使用星号*代表任意
3,包名与类名之间一个点.代表当前包下的类,两个点..表示当前包及其子包下的类
4,参数列表可以使用两个点..表示任意个数,任意类型的参数列表
第六步:测试代码
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {
@Autowired
private TargetInterface target;
@Test
public void text1(){
target.save();
}
}
效果:
在这里我们发现了,两个不同的方法相当于一起运行了,但他们却没有在同一个方法里面相当于为目标方法增加了更多的功能(我们是可以把这个打印话的这个功能变成其他代码逻辑功能呀),说明了解耦合了。
在上面我们只了解到了一个增强方法:前置通知,现在我们来了解一下其他的通知:
通知的全部种类:
然后我们利用代码来进行演示其他类型的通知作用:
后置通知:
在切面中加入后置通知:
public void afterReturnning(){
System.out.println("后置增强...");
}
在配置文件中进行配置:
运行效果:
环绕通知:(因为环绕通知会在目标方法执行的前后都会运行。所以这里面会有个参数,那表示切点的方法。在环绕前通知和环绕后通知之间)
在切面中加入环绕通知:
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕前通知...");
//切点方法
Object proceed = pjp.proceed();
System.out.println("环绕后通知...");
return proceed;
}
对参数进行解释:
ProceedingJoinPoint翻译过来为正在运行的连接点就等于是切点。
然后在代码中间pip.proceed就表示切点方法的运行。这个是有返回值的,所以环绕通知跟其他的通知是有点区别的。
在配置文件中进行配置:
运行效果:
异常抛出通知:
需要将save里面创建一点异常:
在切面中加入异常抛出通知:
public void afterThrowing(){
System.out.println("异常抛出增强...");
}
在配置文件中进行配置:
运行效果:
如果int i=1/0在save running前面,那么save running就不会在控制台打印。
最终通知:
在切面中加入最终通知:
public void after(){
System.out.println("最终增强...");
}
在配置文件中进行配置:
运行效果:
在上面我们发现:我们每个切点表达式都写了好多遍,但他们是一样的。所以我们可以进行切点表达式的抽取:(这样有利于我们维护)
我们在配置文件进行增加抽取切点表达式:(需要在声明切面的内部)
我们用环绕通知来演示:
效果: