Joinpoint(连接点):连接点是指哪些被拦截到的点,在spring中,这些点指的是方法,因为spring只支持方法类型的连接点
Pointcut(切入点):指我们要对哪些连接点进行拦截的定义
Advice(通知/增强):拦截到连接点之后要做的事情 通知的类型:前置通知(在调用invoke方法前),后置通知(在调用invoke方法后),异常通知(在try语句中),最终通知(在finally语句中),环绕通知(整个增强方法)。
Target(目标对象):代理的目标对象。
Weaving(织入):指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类。
Aspect(切面):是切入点和通知(引介)的结合。
在bean.xml中将切面类注入到Spring容器中
<!-- 配置Logger类 --> <bean id="logger" class="yonmin.utils.Logger"></bean>使用<aop:config>标签配置AOP
<!-- 配置Logger类 --> <bean id="logger" class="yonmin.utils.Logger"></bean> <!-- 配置AOP --> <aop:config> ... </aop:config>在<aop:config>标签内部配置切入点(可省略),即被增强的方法。 id属性是通知引用切入点时的标识,expression是切面表达式,需导入aspectjweaver,具体写法见下文。 注意:切入点可以写在配置切面的内部,也可以写在外部。但是写在外部时要将配置切入点写在配置切面的前面。
<!-- 配置Logger类 --> <bean id="logger" class="yonmin.utils.Logger"></bean> <!-- 配置AOP --> <aop:config> <!-- 配置切入点 --> <aop:pointcut id="pt1" expression="execution(* yonmin.service.impl.*.*(..))"/> </aop:config>在<aop:config>标签内部配置切面,即切入点和通知(引介)的结合。切面的ref属性是导入Ioc容器中的切面类的id
<!-- 配置AOP --> <aop:config> <!-- 配置切入点 --> <aop:pointcut id="pt1" expression="execution(* yonmin.service.impl.*.*(..))"/> <!-- 配置切面 --> <aop:aspect id="logAdive" ref="logger"> ... </aop:aspect> </aop:config>在<aop:aspect>标签内部配置通知(增强)。 例如配置前置通知:<aop:before method="" pointcut-ref=""></aop:before> 其中method属性是切面类中的方法名,pointcut-ref属性是配置的切入点的id。也可以不使用配置好的切入点,见下文。 注意配置环绕通知后可能会发生切入点方法没有被执行,但是环绕通知被执行了。此时则需要修改切面类中的环绕通知方法,具体方法在下文。
<!-- 配置Logger类 --> <bean id="logger" class="yonmin.utils.Logger"></bean> <!-- 配置AOP --> <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:config>标签表明开始AOP配置
使用<aop:aspect>标签表明配置切面
id:给切面提供一个唯一标识ref:指定通知类bean的id在<aop:aspect>标签内部使用对应标签来配置通知的类型
aop:before:表示配置前置通知
method:用于指定Logger类中哪个方法是前置通知pointcut:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强切入点表达式写法(需导入aspectjweaver):
关键字:execution(表达式)表达式: 访问修饰符 返回值 包名.包名…类名.方法名(参数列表)标准的表达式写法 public void yonmin.service.impl.AccountServiceImpl.saveAccount()访问修饰符可以省略 void yonmin.service.impl.AccountServiceImpl.saveAccount()返回值可以使用返回值通配符 * yonmin.service.impl.AccountServiceImpl.saveAccount()包名可以使用通配符表示任意包。但是有几级包,就需要写几个*. * *.*.*.*.AccountServiceImpl.saveAccount()包名可以使用…表示当前包及其子包 * *..AccountServiceImpl.saveAccount()类名和方法名都可以使用*实现通配 * *..*.*()参数列表 可以直接写数据类型: 基本类型直接写名称,如:int引用类型写包名.类名的方式,如:java.lang.String 类型可以使用通配符 * 来表示任意类型,但必须有参数可以使用 … 表示有无参数均可,有参数可以是任意类型 全通配写法(最好不要这样写): * *..*.*(..)实际开发中切入点表达式的通常写法 例如切到业务层实现类下的所有写法 * yonmin.service.impl.*.*(..) <!-- 配置Logger类 --> <bean id="logger" class="yonmin.utils.Logger"></bean> <!-- 配置AOP --> <aop:config> <!-- 配置切面 --> <aop:aspect id="logAdive" ref="logger"> <aop:before method="beforePrintLog" pointcut="execution(* yonmin.service.impl.*.*(..))"></aop:before> </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注解。
<?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" 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"> <!-- 配置spring创建容器时扫描的包 --> <context:component-scan base-package="yonmin"></context:component-scan> <!-- 使AOP支持注解 --> <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文件运行即可