我们知道Spring-AOP的核心执行流程是这样的.
1. Spring Ioc初始化所有Bean. 如果初始化Bean的时候发现改Bean满足pointcut中SpEl表达式, 则将该Bean做动态代理.
2. 在Bean的方法执行时,判断该Bean是否为代理对象,若是代理对象的话, 判断当前Method是否满足aop表达式,
3. 如果满足表达式,则按照顺序通知Aop配置的各通知(前,后,环绕,异常等通知)
所以在配置Aop时,我们按照以下步骤:
1. 定义一个Aop通知对象(切面)
2. 配置xml(或者注解), 让被通知对象与aspect通过表达式进行匹配
准备:
1. 先定义Bean对象
public interface ISleepable {
public void sleep();
}
public class Human implements ISleepable{
@Override
public void sleep() {
System.out.println(" 小主启动睡觉核心程序...........");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2. 再定义两个被通知主体:
public class SleepHelper {
public void afterReturning() throws Throwable {
System.out.println("业余催眠师 催眠结束, 向家人收钱..........");
}
public void before() throws Throwable {
System.out.println("业余催眠师 开始催眠..........");
}
}
public class SleepLog {
public void before() throws Throwable {
System.out.println(">>>>>>> 小主准备脱衣睡觉 ");
}
public void afterReturning() throws Throwable {
System.out.println(">>>>>>> 小主睡着了, 自动关灯: ");
}
}
3. 用表达式粘合:
<!--bean对象-->
<bean id="human" class="com.spring_aop.Human"></bean>
<!--两个通知主体-->
<bean id="yeyuSleepHelper" class="com.spring_aop.SleepHelper"></bean>
<bean id="SleepLog" class="com.spring_aop.SleepLog"></bean>
<aop:config proxy-target-class="true">
<!--1. 定义默认的全局切点-->
<aop:pointcut expression="execution(* com.spring_aop.Human.*(..))" id="pointCutDefault"/>
<aop:aspect ref="yeyuSleepHelper" id="aspect" order="1">
<aop:before method="before" pointcut-ref="pointCutDefault"/>
<aop:after-returning method="afterReturning" pointcut-ref="pointCutDefault"/>
</aop:aspect>
<aop:aspect ref="SleepLog" id="aspectlog" order="3">
<aop:before method="before" pointcut-ref="pointCutDefault"/>
<aop:after-returning method="afterReturning" pointcut-ref="pointCutDefault"/>
</aop:aspect>
<!--============ 一条美丽的分割线 ==========-->
<aop:aspect ref="yeyuSleepHelper" id="aspect4" order="2">
<!--2. 这里的pointcut定义在aspect内, -->
<aop:pointcut expression="execution(* com.spring_aop.Human.*(..))" id="pointCutSelfDefined"/>
<aop:before method="before" pointcut-ref="pointCutSelfDefined"/>
<aop:after-returning method="afterReturning" pointcut-ref="pointCutSelfDefined"/>
</aop:aspect>
</aop:config>
注意: 我们将<aop:pointcut>
直接配置在<aop:config>
一级目录, 表示这是个全局默认配置,
这样可以在多个<aop:aspect>
中直接引用此<aop:pointcut>
,
当然也可以在<aop:aspect>
内部单独定义<aop:pointcut>
,如
<aop:aspect ref="yeyuSleepHelper" id="aspect4" order="2">
<!--2. 这里的pointcut定义在aspect内, -->
<aop:pointcut expression="execution(* com.spring_aop.Human.*(..))" id="pointCutSelfDefined"/>
<aop:before method="before" pointcut-ref="pointCutSelfDefined"/>
<aop:after-returning method="afterReturning" pointcut-ref="pointCutSelfDefined"/>
</aop:aspect>
备注: 每个<aop:aspect>
都有属性order
用来定义切面的执行顺序, 默认是按照xml解析顺序执行
除了直接使用普通Bean定义切面,然后在xml中手动指定其method接受各类别的通知Advice外,我们也可以专门定义一个类来处理接受到的通知. 如:
public class SleepHelperAdvisor implements MethodBeforeAdvice, AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("专业催眠师 催眠结束, 向家人收钱..........");
}
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("专业催眠师 催眠师开始催眠..........");
}
}
注意, 需要实现相应的接口:AfterReturningAdvice
,MethodBeforeAdvice
,
在xml中配置Advisor:
包含三种方式;
第一种:
可以认为advisor 是一种特殊的Aspet, 其内部包含一个Advice实现类和一个Pointcut 表达式,
所以可以直接在<aop:advisor>
中直接配置pointcut表达式.
所以可以如下定义
<aop:config>
<!-- 注意advisor不能放在aspect的后面-->
<aop:advisor advice-ref="professionSleeper"
pointcut="execution(* com.spring_aop.Human.*(..))" order="4"/>
</aop:config>
第二种:
在<aop:advisor>
中 使用已经配置好的pointcut
<aop:config>
<aop:advisor advice-ref="professionSleeper" pointcut-ref="pointCutDefault" order="5"/>
</aop:config>
第三种:
在<aop:advisor>
中 使用自定义的pointcut
<aop:config proxy-target-class="true">
<aop:pointcut id="pointAdvisorDefined" expression="execution(* com.spring_aop.Human.*(..))"/>
<aop:advisor advice-ref="professionSleeper" pointcut-ref="pointAdvisorDefined" order="1"/>
</aop:config>
advisor使用场景
< aop:advisor>大多用于事务管理。
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" timeout="120" propagation="REQUIRED" rollback-for="Exception" />
</tx:attributes>
</tx:advice>
<aop:config proxy-target-class="true">
<aop:pointcut id="txPointCut" expression="..."/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut" />
</aop:config>
小结:
<aop:advisor>
和<aop:aspect>
其实都是将通知和切面进行了封装,原理基本上是一样的,只是使用的方式不同而已。
注意点:
- Aop动态代理如下遵从如下规则, 如果bean有接口, 则默认使用Jdk动态代理, 否则使用Cglib动态代理.
这就会出现如下问题:
@Autowired
Human human;
当我们在Spring中自动注入时使用实现类而非接口时, 因为Human有接口,所以会被动态代理为一个实现其接口的代理对象,从而找不到原始的Human, 找出注入失败.
有两种解决方法
1. 注入时,使用接口如:
@Autowired
ISleepable human;
2.. 定义动态代理时指定强制使用CgLib, 配置"proxy-target-class="true"
因为Cglib动态代理实现的是继承机制, 会返回当前类的子类,
最后附录上全部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
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
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">
<aop:aspectj-autoproxy proxy-target-class="true"/>
<!--bean对象-->
<bean id="human" class="com.spring_aop.Human"></bean>
<!--两个通知主体-->
<bean id="yeyuSleepHelper" class="com.spring_aop.SleepHelper"></bean>
<bean id="SleepLog" class="com.spring_aop.SleepLog"></bean>
<aop:config proxy-target-class="true">
<!--定义默认的全局切点-->
<aop:pointcut expression="execution(* com.spring_aop.Human.*(..))" id="pointCutDefault"/>
<aop:aspect ref="yeyuSleepHelper" id="aspect" order="1">
<aop:before method="before" pointcut-ref="pointCutDefault"/>
<aop:after-returning method="afterReturning" pointcut-ref="pointCutDefault"/>
</aop:aspect>
<aop:aspect ref="SleepLog" id="aspectlog" order="3">
<aop:before method="before" pointcut-ref="pointCutDefault"/>
<aop:after-returning method="afterReturning" pointcut-ref="pointCutDefault"/>
</aop:aspect>
<!--============ 一条美丽的分割线 ==========-->
<aop:aspect ref="yeyuSleepHelper" id="aspect4" order="2">
<!--这里的pointcut定义在aspect内, -->
<aop:pointcut expression="execution(* com.spring_aop.Human.*(..))" id="pointCutSelfDefined"/>
<aop:before method="before" pointcut-ref="pointCutSelfDefined"/>
<aop:after-returning method="afterReturning" pointcut-ref="pointCutSelfDefined"/>
</aop:aspect>
</aop:config>
<bean id="professionSleeper" class="com.spring_aop.SleepHelperAdvisor"></bean>
<aop:config>
<!--advisor不能放在aspect的后面-->
<aop:advisor advice-ref="professionSleeper" pointcut="execution(* com.spring_aop.Human.*(..))" order="4"/>
<aop:advisor advice-ref="professionSleeper" pointcut-ref="pointCutDefault" order="5"/>
</aop:config>
</beans>
源码:
https://gitee.com/yangxulong/gitee-projects/tree/aop/spring-stuffs
完…