Joinpoint(连接点):连接点是指哪些被拦截到的点,在spring中,这些点指的是方法,因为spring只支持方法类型的连接点
Pointcut(切入点):指我们要对哪些连接点进行拦截的定义
Advice(通知/增强):拦截到连接点之后要做的事情
通知的类型:前置通知(在调用invoke方法前),后置通知(在调用invoke方法后),异常通知(在try语句中),最终通知(在finally语句中),环绕通知(整个增强方法)。
Target(目标对象):代理的目标对象。
Weaving(织入):指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类。
Aspect(切面):是切入点和通知(引介)的结合。
在bean.xml中将切面类注入到Spring容器中
<bean id="logger" class="yonmin.utils.Logger">bean>
使用
标签配置AOP
<bean id="logger" class="yonmin.utils.Logger">bean>
<aop:config>
...
aop:config>
在
标签内部配置切入点(可省略),即被增强的方法。
id属性是通知引用切入点时的标识,expression是切面表达式,需导入aspectjweaver
,具体写法见下文。
注意:切入点可以写在配置切面的内部,也可以写在外部。但是写在外部时要将配置切入点写在配置切面的前面。
<bean id="logger" class="yonmin.utils.Logger">bean>
<aop:config>
<aop:pointcut id="pt1" expression="execution(* yonmin.service.impl.*.*(..))"/>
aop:config>
在
标签内部配置切面,即切入点和通知(引介)的结合。切面的ref属性是导入Ioc容器中的切面类的id
<aop:config>
<aop:pointcut id="pt1" expression="execution(* yonmin.service.impl.*.*(..))"/>
<aop:aspect id="logAdive" ref="logger">
...
aop:aspect>
aop:config>
在
标签内部配置通知(增强)。
例如配置前置通知:
其中method属性是切面类中的方法名,pointcut-ref属性是配置的切入点的id。也可以不使用配置好的切入点,见下文。
注意配置环绕通知后可能会发生切入点方法没有被执行,但是环绕通知被执行了。此时则需要修改切面类中的环绕通知方法,具体方法在下文。
<bean id="logger" class="yonmin.utils.Logger">bean>
<aop:config>
<aop:pointcut id="pt1" expression="execution(* yonmin.service.impl.*.*(..))"/>
<aop:aspect id="logAdive" ref="logger">
<aop:before method="beforePrintLog" pointcut-ref="pt1">aop:before>
<aop:after-returning method="afterPrintLog" pointcut-ref="pt1">aop:after-returning>
<aop:after-throwing method="exceptPrintLog" pointcut-ref="pt1">aop:after-throwing>
<aop:after method="finalPrintLog" pointcut-ref="pt1" >aop:after>
<aop:around method="aroundPrintLog" pointcut-ref="pt1">aop:around>
aop:aspect>
aop:config>
开发阶段(我们做的)
编写核心业务代码(开发主线):大部分程序员都能做,要求熟悉业务需求。
把公用代码抽取出来,制作成通知。(开发阶段最后再做):A0P编程人员来做。
在配置文件中,声明切入点与通知间的关系,即切面。:AOP编程人员来做。
运行阶段( Spring框架完成的)
Spring框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
Spring中基于XML的AOP配置步骤
把通知Bean交给spring管理
使用
标签表明开始AOP配置
使用
标签表明配置切面
在
标签内部使用对应标签来配置通知的类型
aop:before:表示配置前置通知
切入点表达式写法(需导入aspectjweaver
):
<bean id="logger" class="yonmin.utils.Logger">bean>
<aop:config>
<aop:aspect id="logAdive" ref="logger">
<aop:before method="beforePrintLog" pointcut="execution(* yonmin.service.impl.*.*(..))">aop:before>
aop:aspect>
aop:config>
<bean id="logger" class="yonmin.utils.Logger">bean>
<aop:config>
<aop:pointcut id="pt1" expression="execution(* yonmin.service.impl.*.*(..))"/>
<aop:aspect id="logAdive" ref="logger">
<aop:before method="beforePrintLog" pointcut-ref="pt1">aop:before>
<aop:after-returning method="afterPrintLog" pointcut-ref="pt1">aop:after-returning>
<aop:after-throwing method="exceptPrintLog" pointcut-ref="pt1">aop:after-throwing>
<aop:after method="finalPrintLog" pointcut-ref="pt1" >aop:after>
<aop:around method="aroundPrintLog" pointcut-ref="pt1">aop:around>
aop:aspect>
aop:config>
环绕通知:
在日志代码中添加环绕通知并且在bean.xml中配置好,运行程序发现环绕通知执行了,但是切入点的方法没有执行
在Logger代码中如此配置环绕通知后切入点方法即被执行。
重点是使用 ProceedingJoinPoint
,明确调用业务层方法(切入点方法)
public Object aroundPrintLog(ProceedingJoinPoint pjp){
Object rtValue = null;
try {
Object[] args = pjp.getArgs();
System.out.println("前置通知");
rtValue = pjp.proceed(args); // 明确调用业务层方法(切入点方法)
System.out.println("后置通知");
} catch (Throwable throwable) {
System.out.println("异常通知");
throwable.printStackTrace();
}finally {
System.out.println("最终通知");
}
return rtValue;
}
bean.xml文件中如此配置使代码支持AOP注解。
<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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="yonmin">context:component-scan>
<aop:aspectj-autoproxy>aop:aspectj-autoproxy>
beans>
使用@Aspect
表示修饰类是一个切面类。
在切面类中的方法上使用:
@Before
:表示前置通知
@AfterReturning
:表示后置通知
@AfterThrowing
:表示异常通知
@After
:表示最终通知
在切面类中使用如下代码创建名为pt1
的切面对象,注意在诸如@Before("pt1()")
引用时,一定要在名字后面加上括号。
//创建切面对象
@Pointcut("execution(* yonmin.service.impl.*.*(..))")
private void pt1(){}
此时运行程序发现输出如下
Log类中的beforePrintLog方法开始记录日志了。。。
执行了保存
Log类中的finalPrintLog方法开始记录日志了。。。
Log类中的afterReturningPrintLog方法开始记录日志了。。。
发现顺序出现错误,这是Spring中AOP自带的问题,解决方法是使用环绕通知。
在环绕通知的方法上添加注解@Around
@Around("pt1()")
public Object aroundPrintLog(ProceedingJoinPoint pjp){
Object rtValue = null;
try {
Object[] args = pjp.getArgs();
System.out.println("前置通知");
rtValue = pjp.proceed(args); // 明确调用业务层方法(切入点方法)
System.out.println("后置通知");
} catch (Throwable throwable) {
System.out.println("异常通知");
throwable.printStackTrace();
}finally {
System.out.println("最终通知");
}
return rtValue;
}
去除其它通知注解后运行,输出
前置通知
执行了保存
后置通知
最终通知
显然没有出现顺序错误的问题。
新建一个配置类,内容如下
package yonmin.configration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan("yonmin")
@EnableAspectJAutoProxy
public class SpringConfigration {
}
@Configuration说明此类是Spring的配置类
@ComponentScan(“yonmin”) 相当于XML文件中扫描包的代码
@EnableAspectJAutoProxy表明能够使用AOP注解
在test代码中使用Junit进行测试
package test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import yonmin.configration.SpringConfigration;
import yonmin.service.IAccountService;
import javax.annotation.Resource;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfigration.class)
public class AOPTest {
@Resource(name = "accountService")
private IAccountService as;
@Test
public void test(){
as.saveAccount();
}
}
使用@Resource(name = "accountService")
获取Ioc容器中的accountService对象。
此时删除bean.xml文件运行即可