AOP
AOP概述
- 面向切面编程
- OOP(面向对象编程纵向继承体系)的延伸
- 横向抽取机制
- 应用
术语
- AOP那些学术概念—通知、增强处理连接点(JoinPoint)切面(Aspect)
- JoinPoint (拦截点)所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点.
- PointCut (切入点)所谓切入点是指我们要对哪些Joinpoint进行拦截的定义.
- Advice (通知)所谓通知是指拦截到Joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)
- Introduction (引介)引介是一种特殊的通知在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field.
- Target (目标对象)代理的目标对象
- Weaving (织入)是指把增强应用到目标对象来创建新的代理对象的过程,spring采用动态代理织入,而AspectJ采用编译期织入和类装在期织入
- Proxy(代理)一个类被AOP织入增强后,就产生一个结果代理类
- Aspect(切面)是切入点和通知(引介)的结合
AOP底层实现
- 代理机制
- JDK动态代理
- 对实现了接口的类生成代理
- CGLIB动态代理
- 对没有实现接口的类产生代理.产生这个类的子类对象。
- JDK动态代理
Spring的传统AOP的开发(了解即可)
- 核心
ProxyFactoryBean
对象
- target : 代理的目标对象
- proxyInterfaces : 代理要实现的接口
- 如果多个接口可以使用以下格式赋值
<list>
<value></value>
....
</list>
- proxyTargetClass : 是否对类代理而不是接口,设置为true时,使用CGLib代理
- interceptorNames : 需要织入目标的Advice
- singleton : 返回代理是否为单实例,默认为单例
- optimize : 当设置为true时,强制使用CGLib
- Advice 通知的类型
- 前置通知
MethodBeforeAdvice
- 后置通知
AfterReturningAdvice
- 环绕通知
MethodInterceptor
- 异常抛出通知
ThrowsAdvice
- 引介通知
IntroductionInterceptor
- 前置通知
- 切面类型
Advisor
代表一般切面,Advice本身就是一个切面,对目标类所有方法进行拦截.(不带切入点切面:增强所有方法)PointcutAdvisor
代表具有切点的切面,可以指定拦截目标类哪些方法.(带有切入点切面:增强某些方法)IntroductionAdvisor
代表引介切面,针对引介通知而使用切面(不要求掌握)
- 不带切入点的例子
<!-- 配置目标类: -->
<bean id="productDao" class="com.itheima.spring.demo3.ProductDaoImpl"/>
<!-- 配置通知:(前置通知) -->
<bean id="beforeAdvice" class="com.itheima.spring.demo3.MyBeforeAdvice"></bean>
<!-- 配置生成代理 -->
<bean id="productDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 配置目标类 -->
<property name="target" ref="productDao"/>
<!-- 配置类的实现的接口 -->
<property name="proxyInterfaces" value="com.itheima.spring.demo3.ProductDao"/>
<!-- 配置切面要拦截的名称 -->
<property name="interceptorNames" value="beforeAdvice"/>
</bean>
- 带切入点的切面
<!-- 配置通知:(环绕通知) -->
<bean id="myAroundAdvice" class="com.itheima.spring.demo4.MyAroundAdvice"/>
<!-- 配置带有切入点的切面 -->
<bean id="myAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<!-- 表达式: 正则表达式 : .:任意字符 *:任意次数-->
<property name="pattern" value=".*"/>
<!-- 配置增强 -->
<property name="advice" ref="myAroundAdvice"/>
</bean>
<!-- 配置生成代理 -->
<bean id="customerDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 配置目标 -->
<property name="target" ref="customerDao"/>
<!-- 配置代理目标类 -->
<property name="proxyTargetClass" value="true"/>
<!-- 配置拦截的名称 -->
<property name="interceptorNames" value="myAdvisor"/>
</bean>
Spring的传统AOP的开发:自动代理
自动代理的方式
BeanNameAutoProxyCreator
:根据Bean名称创建代理DefaultAdvisorAutoProxyCreator
:根据Advisor本身包含信息创建代理AnnotationAwareAspectJAutoProxyCreator
:基于Bean中的AspectJ 注解进行自动代理
实现机制
- BeanPostProcessor后处理器
- 在类的生成过程中就产生了代理.
基于BeanName的自动代理
<!-- 配置目标类: -->
<bean id="productDao" class="com.itheima.spring.demo3.ProductDaoImpl"/>
<bean id="customerDao" class="com.itheima.spring.demo4.CustomerDao"/>
<!-- 配置通知:(前置通知) -->
<bean id="beforeAdvice" class="com.itheima.spring.demo3.MyBeforeAdvice"/>
<!-- 配置通知:(环绕通知) -->
<bean id="myAroundAdvice" class="com.itheima.spring.demo4.MyAroundAdvice"/>
<!-- 配置基于Bean名称的自动代理 (自动加载,id可以省略)-->
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<!-- 配置Bean名称 -->
<property name="beanNames" value="*Dao"/>
<!-- 配置拦截的名称 -->
<property name="interceptorNames" value="beforeAdvice"/>
</bean>
- 基于切面的自动代理
<!-- 配置目标类: -->
<bean id="productDao" class="com.itheima.spring.demo3.ProductDaoImpl"/>
<bean id="customerDao" class="com.itheima.spring.demo4.CustomerDao"/>
<!-- 配置通知:(前置通知) -->
<bean id="beforeAdvice" class="com.itheima.spring.demo3.MyBeforeAdvice"/>
<!-- 配置通知:(环绕通知) -->
<bean id="myAroundAdvice" class="com.itheima.spring.demo4.MyAroundAdvice"/>
<!-- 配置切面 -->
<bean id="myAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<!-- 表达式 -->
<property name="pattern" value="com\.itheima\.spring\.demo4\.CustomerDao\.save"/>
<!-- 配置增强 -->
<property name="advice" ref="myAroundAdvice"/>
</bean>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
Spring基于AspectJ注解方式的AOP的开发:(当前流行的开发方式)
- 基于AspectJ(面向切面的框架)
- 只需要配置一个
Bean
即可 AspectJ通知类注解(使用在方法上,将目标方法定义为一个通知)
- @Before 前置 Joinpoint:获得切入点信息.
@Before("execution(* com.itheima.spring.demo1.OrderDao.save(..))") public void before(JoinPoint joinPoint){ System.out.println("前置通知================"); } }
@AfterReturning 后置 在return之前 获取返回结果 Object result
// 后置通知: @AfterReturning(value="execution(* com.itheima.spring.demo1.OrderDao.update(..))",returning="result") public void afterReturing(Object result){ System.out.println("后置通知================"+result); }
@Around 环绕 ProceedingJoinPoint joinPoint 切入点信息
// 环绕通知: @Around("execution(* com.itheima.spring.demo1.OrderDao.delete(..))") public Object around(ProceedingJoinPoint joinPoint) throws Throwable{ System.out.println("环绕前通知==============="); // 执行目标方法: Object obj = joinPoint.proceed(); System.out.println("环绕后通知==============="); return obj; }
@AfterThrowing 异常抛出 Throwable 获取异常信息
// 异常抛出通知: @AfterThrowing(value="execution(* com.itheima.spring.demo1.OrderDao.find(..))",throwing="e") public void afterThrowing(Throwable e){ System.out.println("异常抛出通知=============="+e.getMessage()); }
@After final通知 相当于添加了Final块
// 最终通知 @After(value="execution(* com.itheima.spring.demo1.OrderDao.find(..))") public void after(){ System.out.println("最终通知================"); }
@DeclareParents 引介通知,相当于IntroductionInterceptor
- 切入点表达式
- execution
- 可以理解为 方法的签名 抽象
- 语法
[访问修饰符] 方法返回值 方法名(参数)
- 例子
execution(public * com.itheim.spring.demo1.OrderDao.save(..))
execution(* *.*(..))
execution(public * com.itheim.spring.demo1.*.*(..))
execution(public * com.itheim.spring.demo1..*.*(..))
execution(public * com.itheim.spring.demo1.OrderDao+.*(..))
- 切面
@Aspect
public class MyAspectAnno {
// 定义通知:
@Before("execution(* com.itheima.spring.demo1.OrderDao.save(..))")
public void before(){
System.out.println("前置通知================");
}
}
- 开启
AspectJ
注解
@Aspect
public class MyAspectAnno {
// 定义通知:
@Before("execution(* com.itheima.spring.demo1.OrderDao.save(..))")
public void before(){
System.out.println("前置通知================");
}
}
- 配置切面类
<!-- 配置切面类 -->
<bean class="com.itheima.spring.demo1.MyAspectAnno"/>
基于AspectJ(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"
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="customerDao" class="com.itheima.spring.demo2.CustomerDaoImpl"/>
public class MyAspectXml {
public void before(){
System.out.println("前置通知==============");
}
}
<!-- 配置切面类 -->
<bean id="myAspectXml" class="com.itheima.spring.demo2.MyAspectXml"/>
<!-- 配置AOP -->
<aop:config>
<aop:pointcut expression="execution(* com.itheima.spring.demo2.CustomerDao+.save(..))" id="pointcut1"/>
<aop:aspect ref="myAspectXml">
<aop:before method="before" pointcut-ref="pointcut1"/>
</aop:aspect>
</aop:config>
- 其他类型的通知配置方式
<!-- 配置AOP -->
<aop:config>
<aop:pointcut expression="execution(* com.itheima.spring.demo2.CustomerDao+.save(..))" id="pointcut1"/>
<aop:pointcut expression="execution(* com.itheima.spring.demo2.CustomerDao+.update(..))" id="pointcut2"/>
<aop:pointcut expression="execution(* com.itheima.spring.demo2.CustomerDao+.delete(..))" id="pointcut3"/>
<aop:pointcut expression="execution(* com.itheima.spring.demo2.CustomerDao+.find(..))" id="pointcut4"/>
<aop:aspect ref="myAspectXml">
<aop:before method="before" pointcut-ref="pointcut1"/>
<aop:after-returning method="afterReturing" pointcut-ref="pointcut2" returning="result"/>
<aop:around method="around" pointcut-ref="pointcut3"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut4" throwing="e"/>
<aop:after method="after" pointcut-ref="pointcut4"/>
</aop:aspect>
</aop:config>