文章目录
【README】
本文总结自《spring揭秘》,作者王福强,非常棒的一本书,墙裂推荐;
1)spring aop博文列表:
- spring揭秘07-aop01-aop基本要素及代理模式3种实现
- spring揭秘09-aop02-aop基本要素抽象与通知及切面织入
- spring揭秘09-aop03-aop织入器织入横切逻辑与自动织入
- spring揭秘10-aop04-基于AspectJ类库注解织入横切逻辑
- spring揭秘11-aop05-aop应用经典场景
2)以上aop详细内容总结如下;
【1】aop基本要素定义
【1.1】切点JoinPoint
1)切点JoinPoint: 被织入切面逻辑的程序位置(程序执行点), 如调用方调用目标对象target的方法method1的调用位置或程序执行点;
class Caller {
callerMethod() {
target.method1(); // joinpoint 切点
}
}
【1.2】 切点表达式Pointcut
1)切点表达式Pointcut:描述切点位置的表达式;只有匹配的pointcut表达式的切点才会被织入横切逻辑;(简单理解为是织入条件)
- 表达式包括正则表达式,明确指定bean名称或方法名称或参数类型或参数名称的表达式,描述被注解标注的表达式等,即明确描述哪些bean,哪些方法,哪些参数类型,哪些参数名称的调用要被织入横切逻辑;
【1.3】横切逻辑Advice(通知)
1) 横切逻辑Advice:被织入到切点的横切逻辑;(advice翻译为通知,也就是横切逻辑)
- Before Advice:前置通知;该通知在切点被执行之前执行;
- After Advice:后置通知;该通知在切点被执行之后执行;
- After Returning:该通知在切点正常返回后执行;
- After throwing:该通知在切点抛出异常时(异常返回)执行; 如全局异常处理通知;
- After (finally):该通知在切点正常返回或异常返回后,都被执行;
- Around Advice:环绕通知;该通知在切点执行前与执行后都执行;即上下文织入横切逻辑;
- 环绕通知可以实现(替换) Before, AfterReturning, AfterThrowing,After(finally) 通知的功能;
- Introduction Advice;引入通知;该通知不会织入到目标对象的已有方法(不会影响已有方法),而是为目标对象织入新方法;
2)按照是否引入型通知分类:
- 非引入型通知:Before, After Returning, After Throwing, After(finally), Around;(简单理解:非引入型通知是纵向织入; 织入横切逻辑到已有方法上下文,织入动作影响已有方法逻辑 )
- 引入型通知:Introduction; (简单理解:引入型通知是横向织入;织入新方法,织入动作不影响已有方法 )
【1.4】Aspect切面
1)1个切面可以封装多个 pointcut和advice;
- 但切面实现类Advisor仅封装1个pointcut和1个advice;
【1.5】目标对象与代理对象
1)目标对象: 被织入横切逻辑的对象;
2)代理对象:封装横切逻辑与目标对象方法调用的对象;
- 表面看,织入器把横切逻辑织入到目标对象(或目标方法);实际上,横切逻辑是织入到代理对象;代理对象与目标对象有着相同的方法; 调用代理对象的方法,代理对象根据通知类型执行横切逻辑(前置或后置),接着执行目标对象方法(当然也可以不调用目标对象方法);
- 即通过代理对象调用方法才会被拦截(才会执行横切逻辑),而通过目标对象本身调用方法不会被拦截 ;
class ProxyClass extends TargetClass (or implements TargetInterface) {
adviceMethod(JoinPoint joinPoint) { // joinPoint表示切点(如目标方法调用的程序执行点)
beforeAdvice(); // 前置通知 或 环绕通知的前置通知
joinPoint.proceed(); // 调用目标对象的方法(或有;因为可以不调用,如权限校验不通过)
afterAdvice(); // 环绕通知的后置通知
} catch(Exception e) {
afterThrowingAdvide(e); // 异常返回的后置通知
} finally {
afterFinallyAdvice(); // 正常返回或异常返回的后置通知
}
}
3)目标对象与代理对象间方法调用链:
【1.6】关联关系
1)1个目标对象可以有多个切面;
2)1个切面可以对应多个pointcut 与 多个advice;
【2】代理模式
1)代理模式有3种实现方式:
- 静态代理:要求目标类与代理类都实现相同接口;不灵活;
- JDK动态代理:要求目标类实现接口,而代理类需要实现 InvocationHandler接口,重写invoke方法;织入横切逻辑后, 目标对象与代理对象实现相同接口 ,它们是兄弟;
- CGLIB动态代理: 采用CGLIB动态字节码生成技术构建代理对象,无需目标类与横切逻辑所在类(代理类)实现接口;织入横切逻辑后,代理对象继承目标对象,它们是父子;
【3】手工织入横切逻辑
1)spring aop 通过织入器织入横切逻辑, 织入方式如下;
【3.1】硬编码手动织入(织入器使用ProxyFactory)
1)简单理解:硬编码指的是没有xml配置,没有注解,全部通过手工创建bean并手工装配bean;
【3.1.1】class级别织入(非引入型通知)
1)aop基本要素抽象:
- pointcut:使用 NameMatchMethodPointcut 抽象切点表达式;
- advice:使用Advice抽象横切逻辑;Advice是接口,横切逻辑类需要实现接口,包括 MethodBeforeAdvice, AfterReturningAdvice, BeforeAdvice, MethodInterceptor(环绕通知);
- Aspect:使用DefaultPointcutAdvisor抽象切面;需要传入pointcut和advice;(advisor仅封装一个pointcut和一个advice)
- 织入器:使用 ProxyFactory 抽象织入器; 传入切面给织入器;(也可以仅传入advice,不传入pointcut;但ProxyFactory 底层会新建DefaultPointcutAdvisor封装advice,pointcut默认取 Pointcut.true,即匹配所有切点)
- 目标类:分2种情况:
- 目标类已实现接口,则使用JDK动态代理(也可以使用CGLIB动态代理);
- 目标类没有实现接口,则使用CGLIB动态代理;
- 补充: 设置 ProxyFactory.proxyTargetClass=true,则明确使用CGLIB动态代理; 或者目标类没有实现接口,默认使用CGLIB动态代理;
2)使用【DefaultPointcutAdvisorMain】硬编码织入横切逻辑main
【DefaultPointcutAdvisorMain】硬编码织入横切逻辑main
public class DefaultPointcutAdvisorMain {
public static void main(String[] args) {
// 构建匹配切点位置的表达式
NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
pointcut.setMappedName("call");
// 构建横切逻辑
Advice beforeAdvice = new MethodRequestLogBeforeAdviceImpl();
// 组装切面
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, beforeAdvice);
// 新建织入器,并装配目标对象和切面
ManNoItfCallTask target = new ManNoItfCallTask();
ProxyFactory proxyFactory = new ProxyFactory(target);
proxyFactory.addAdvisor(advisor);
// 织入器织入并获取代理对象
ManNoItfCallTask proxy = (ManNoItfCallTask) proxyFactory.getProxy();
proxy.call(BusiMessage.build("task001", "您有待办任务需要处理"));
}
}
【3.1.2】对象级别织入(引入型通知)
1)引入型通知应用场景: 公办学校老师主要工作是在学校讲课, 方法为 teach(); 部分老师在课外培训学校兼职讲课,新接口为ITrainingSchoolTeacher,新方法为 trainAfterSchool() ; 即培训学校新接口及新方法作为引入型通知织入到公办学校老师目标对象,实现公办学校老师兼职课外培训的目的(注意:引入型通知是横向织入,织入动作不影响目标对象已有方法) ;
2)aop基本要素抽象:
- pointcut:无需;引入型通知是明确织入给定对象,所以不需要pointcut;
- advice:使用委派引入型通知抽象。委派的意思是,调用方调用委派通知,委派通知再把调用请求委派给具体通知实现类处理(如TrainingSchoolTeacherImpl); (注意:被委派的具体通知必须实现接口)
- spring给定的静态委派引入型通知:DelegatingIntroductionInterceptor ;
- spring给定的动态委派引入型通知:DelegatePerTargetObjectIntroductionInterceptor;
- Aspect:使用DefaultIntroductionAdvisor抽象切面;仅传入advice;
- 织入器:使用 ProxyFactory 抽象织入器; ( 虽然仅传入了advice,但底层会新建DefaultIntroductionAdvisor,并封装advice )
- 目标类:只能基于CGLIB动态代理的AOP,不要求目标对象实现接口;
【DynamicIntroductionAdviceMain】动态引入型通知测试入口main
public class DynamicIntroductionAdviceMain {
public static void main(String[] args) {
// 新建目标对象
PublicSchoolTeacher target = new PublicSchoolTeacher();
// 新建织入器
ProxyFactory weaver = new ProxyFactory(target);
// 使用CGLIB实现动态代理,因为目标对象没有实现接口(而不是JDK动态代理)
weaver.setProxyTargetClass(true);
// 设置动态引入型通知横切逻辑的接口, 织入器装配动态引入型通知
weaver.setInterfaces(ITrainingSchoolTeacher.class);
DelegatePerTargetObjectIntroductionInterceptor delegateIntroductionAdvice =
new DelegatePerTargetObjectIntroductionInterceptor(TrainingSchoolTeacherImpl.class, ITrainingSchoolTeacher.class);
weaver.addAdvice(delegateIntroductionAdvice);
// 织入器织入并获取代理对象
Object proxy = weaver.getProxy();
((PublicSchoolTeacher) proxy).teach(); // 代理对象转为目标对象并调用已有方法
((ITrainingSchoolTeacher) proxy).trainAfterSchool(); // 代理对象转为横切逻辑接口对象并调用新方法
}
}
【3.2】工厂模式结合dtdXml织入(织入器使用ProxyFactoryBean)
1)ProxyFactoryBean: Proxy FactoryBean,即创建代理对象的FactoryBean,底层使用工厂模式;(简单理解:通过工厂模式创建proxy)
- 如果spring容器中有对象依赖于 ProxyFactoryBean, 它将会使用 ProxyFactoryBean#getObject() 方法返回的代理对象;
2)ProxyFactoryBean#getObject() 获取代理对象步骤清单如下(以获取代理过的单例bean为例-getSingletonInstance()方法)。
- 第1步:调用 ProxyCreatorSupport#createAopProxy() , 创建Aop代理;
- 第2步:传入Aop代理到getProxy()方法 获取代理对象;
3)使用工厂模式织入,代码比硬编码织入简单;
【3.2.1】基于接口的工厂模式织入
1)基于接口的织入:要求目标类实现接口;底层可以使用JDK动态代理,也可以使用CGLIB动态代理;
2)aop基本要素抽象:
- pointcut:使用 NameMatchMethodPointcut 抽象切点表达式,通过xml配置注入spring容器;
- advice:使用Advice抽象横切逻辑;通过xml配置注入spring容器;
- Aspect:使用DefaultPointcutAdvisor抽象切面;通过xml配置注入spring容器;
- 织入器:使用 ProxyFactoryBean工厂模式抽象织入器;传入目标类,切面;通过xml配置注入spring容器;
public class BaseItfProxyFactoryBeanMain {
public static void main(String[] args) {
ClassPathXmlApplicationContext container =
new ClassPathXmlApplicationContext("chapter09/beans09proxyfactorybeanbasedItf.xml");
ICallTask callTask = (ICallTask) container.getBean("robotCallTaskImplProxy");
callTask.call(BusiMessage.build("任务编号01", "您有待办任务需要处理"));
}
}
【beans09proxyfactorybeanbasedItf.xml】配置spring容器织入器ProxyFactoryBean,织入通知到目标对象(基于接口,使用JDK动态代理)
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 注册切点表达式 -->
<bean id="pointcut" class="org.springframework.aop.support.NameMatchMethodPointcut">
<property name="mappedName" value="call" />
</bean>
<!-- 注册横切逻辑 -->
<bean id="timeCostMethodInterceptor" class="com.tom.springnote.common.aop.methodinterceptor.TimeCostMethodInterceptorImpl" />
<!-- 注册切面 -->
<bean id="advisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="pointcut" ref="pointcut" />
<property name="advice" ref="timeCostMethodInterceptor" />
</bean>
<!-- 注册 Proxy FactoryBean scope=prototype指定原型bean,singleton指定单例bean-->
<bean id="robotCallTaskImplProxy" class="org.springframework.aop.framework.ProxyFactoryBean" scope="prototype">
<property name="target">
<bean id="robotCallTaskImpl" class="com.tom.springnote.chapter09.proxyfactory.baseitfproxy.RobotCallTaskImpl" />
</property>
<property name="proxyTargetClass" value="true" /> <!-- proxyTargetClass=true表示使用CGLIB代理,否则使用JDK动态代理 -->
<!--proxyInterfaces 指定目标对象接口 可以省略 -->
<!-- <property name="proxyInterfaces">-->
<!-- <list>-->
<!-- <value>com.tom.springnote.common.aop.ICallTask</value>-->
<!-- </list>-->
<!-- </property>-->
<!--指定多个将要织入到目标对象的切面,通知或者Interceptor拦截器-->
<property name="interceptorNames">
<list>
<value>advisor</value>
</list>
</property>
</bean>
</beans>
【3.2.2】基于对象的工厂模式织入
1)使用ProxyFactoryBean织入器把通知或切面织入到目标对象; 不需要目标对象实现接口;(底层使用CGLIB代理)
【BaseClassIntroductionProxyFactoryBeanMain】使用ProxyFactoryBean织入器织入引入型通知
public class BaseClassIntroductionProxyFactoryBeanMain {
public static void main(String[] args) {
ClassPathXmlApplicationContext container =
new ClassPathXmlApplicationContext("chapter09/beans09proxyfactorybeanbasedclassintroduction.xml");
Object proxyBean = container.getBean("introducedRobotCallTaskImplProxy");
Object proxyBean2 = container.getBean("introducedRobotCallTaskImplProxy");
// 转为 ICallTask 类型
ICallTask callTask = (ICallTask) proxyBean;
callTask.call(BusiMessage.build("任务编号01", "您有待办任务需要处理"));
// 转为引入型接口1的对象
IIntroduceMethodInvokeCounter introducedMethodInvokeCounter = (IIntroduceMethodInvokeCounter) proxyBean;
introducedMethodInvokeCounter.getCounter();
introducedMethodInvokeCounter.getCounter();
// 第2个bean调用
((IIntroduceMethodInvokeCounter) proxyBean2).getCounter();
((IIntroduceMethodInvokeCounter) proxyBean2).getCounter();
// 转为引入型接口2的对象
IIntroduceMethodAccessLog introduceMethodAccessLog = (IIntroduceMethodAccessLog) proxyBean;
introduceMethodAccessLog.sendAccessLog();
}
}
【beans09proxyfactorybeanbasedclassintroduction.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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 注册切点表达式 -->
<bean id="pointcut" class="org.springframework.aop.support.NameMatchMethodPointcut">
<property name="mappedName" value="call" />
</bean>
<!-- 注册横切逻辑 -->
<bean id="timeCostMethodInterceptorImpl" class="com.tom.springnote.common.aop.methodinterceptor.TimeCostMethodInterceptorImpl" />
<!-- 注册切面 -->
<bean id="advisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="pointcut" ref="pointcut" />
<property name="advice" ref="timeCostMethodInterceptorImpl"/>
</bean>
<!-- 注册 introduction ProxyFactoryBean scope=prototype指定原型bean,singleton指定单例bean-->
<bean id="introducedRobotCallTaskImplProxy" class="org.springframework.aop.framework.ProxyFactoryBean" scope="prototype">
<property name="target">
<bean id="robotCallTaskImpl" class="com.tom.springnote.chapter09.proxyfactory.baseitfproxy.RobotCallTaskImpl" scope="prototype" />
</property>
<property name="proxyInterfaces">
<list>
<value>com.tom.springnote.common.aop.ICallTask</value>
<value>com.tom.springnote.common.aop.IIntroduceMethodInvokeCounter</value>
<value>com.tom.springnote.common.aop.IIntroduceMethodAccessLog</value>
</list>
</property>
<property name="interceptorNames">
<list>
<value>advisor</value>
<value>delegatingIntroductionInterceptor</value>
<value>delegatingIntroductionInterceptor2</value>
</list>
</property>
</bean>
<!-- 静态方法引入拦截器1 -->
<bean id="delegatingIntroductionInterceptor" class="org.springframework.aop.support.DelegatingIntroductionInterceptor" scope="prototype">
<constructor-arg>
<bean class="com.tom.springnote.common.aop.IntroduceMethodInvokeCounterImpl" />
</constructor-arg>
</bean>
<!-- 静态方法引入拦截器2 -->
<bean id="delegatingIntroductionInterceptor2" class="org.springframework.aop.support.DelegatingIntroductionInterceptor">
<constructor-arg>
<bean class="com.tom.springnote.common.aop.IntroduceMethodAccessLogImpl" />
</constructor-arg>
</bean>
</beans>
【4】自动织入横切逻辑
1)问题:使用ProxyFactoryBean织入通知(横切逻辑),需要为每一个目标对象新建一个 ProxyFactoryBean; 一个系统的目标对象非常多,需要大量的配置工作;
- 解决方法:使用自动代理AutoProxy织入通知 ;
2)自动织入实现类-AbstractAutoProxyCreator接口常用实现类:
- BeanNameAutoProxyCreator:通过名字匹配需要织入通知的bean实例 (既然指定了beanName,就不需要pointcut了 ) ;
- DefaultAdvisorAutoProxyCreator: 默认切面自动代理创建者;
- AnnotationAwareAspectJAutoProxyCreator: 通过注解捕获代理信息实现自动织入;
- AspectJAwareAdvisorAutoProxyCreator:AspectJ类库自动织入;
- InfrastructureAdvisorAutoProxyCreator:基础设施切面自动代理织入;
3)自动织入原理: 切面advisor(封装了advice和pointcut)及目标对象注入spring容器后,自动织入器会扫描容器中所有切面,把切面**自动织入**到匹配pointcut的目标对象 ;
【4.1】半自动织入BeanNameAutoProxyCreator
1)半自动织入:使用 BeanNameAutoProxyCreator 作为织入器;使用BeanNameAutoProxyCreator,可以通过指定一组容器内的目标对象对应的beanName,把指定的一组拦截器逻辑织入到目标对象; 批量织入,而不是单个装配pointcut和advice到切面;
【BeanNameAutoProxyCreatorMain】 BeanNameAutoProxyCreator自动织入通知
public class BeanNameAutoProxyCreatorMain {
public static void main(String[] args) {
ClassPathXmlApplicationContext container =
new ClassPathXmlApplicationContext("chapter09/beans09beannameautoproxycreator.xml");
ManNoItfCallTask target1 = container.getBean("target1", ManNoItfCallTask.class);
ManNoItfCallTask target2 = container.getBean("target2", ManNoItfCallTask.class);
// 调用代理对象方法
target1.call(BusiMessage.build("任务001", "您有待办任务需要处理"));
System.out.println("\n=== 我是分割线 ===");
target2.call(BusiMessage.build("任务002", "您有待办任务需要处理"));
}
}
【beans09beannameautoproxycreator.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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 注册横切逻辑 -->
<bean id="timeCostMethodInterceptorImpl" class="com.tom.springnote.common.aop.methodinterceptor.TimeCostMethodInterceptorImpl" />
<bean id="aroundLogMethodInterceptorImpl" class="com.tom.springnote.common.aop.methodinterceptor.AroundLogMethodInterceptorImpl" />
<!-- 目标对象 -->
<bean id="target1" class="com.tom.springnote.common.aop.ManNoItfCallTask" />
<bean id="target2" class="com.tom.springnote.common.aop.ManNoItfCallTask" />
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<list>
<value>target1</value>
<value>target2</value>
</list>
</property>
<property name="interceptorNames">
<list>
<value>timeCostMethodInterceptorImpl</value>
<value>aroundLogMethodInterceptorImpl</value>
</list>
</property>
</bean>
</beans>
【4.2】全自动织入DefaultAdvisorAutoProxyCreator
1)全自动织入:使用 DefaultAdvisorAutoProxyCreator 作为织入器;只针对切面 Advisor有效;
- 无需配置切面与目标对象间的织入关系,由DefaultAdvisorAutoProxyCreator自动识别,自动把切面织入到目标对象;
【DefaultAdvisorAutoProxyCreatorMain】 默认切面自动代理创建者测试main
public class DefaultAdvisorAutoProxyCreatorMain {
public static void main(String[] args) {
ClassPathXmlApplicationContext container =
new ClassPathXmlApplicationContext("chapter09/beans09defaultadvisorautoproxycreator.xml");
// 获取 DefaultAdvisorAutoProxyCreator 自动织入的代理对象
ManNoItfCallTask proxy1 = (ManNoItfCallTask) container.getBean("target1");
ManNoItfCallTask proxy2 = (ManNoItfCallTask) container.getBean("target2");
// 调用代理对象方法
proxy1.call(BusiMessage.build("任务编号001" ,"您有待办任务需要处理"));
System.out.println("\n=== 我是分割线 ===");
proxy2.call(BusiMessage.build("任务编号002" ,"您有待办任务需要处理"));
}
}
【beans09defaultadvisorautoproxycreator.xml】 一个pointcut表达式对应2个advice通知;
底层原理:切面advisor(封装了advice和pointcut)及目标对象注入spring容器后,DefaultAdvisorAutoProxyCreator会扫描容器中所有切面把advice自动注入到匹配pointcut的目标对象 ;
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 注册切面织入全自动代理创建者, 实现自动织入通知 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
<property name="proxyTargetClass" value="true" />
</bean>
<!-- 注册切点表达式 -->
<bean id="pointcut" class="org.springframework.aop.support.NameMatchMethodPointcut">
<property name="mappedName" value="call" />
</bean>
<!-- 注册通知(横切逻辑) -->
<bean id="timeCostMethodInterceptorImpl" class="com.tom.springnote.common.aop.methodinterceptor.TimeCostMethodInterceptorImpl" />
<bean id="aroundLogMethodInterceptorImpl" class="com.tom.springnote.common.aop.methodinterceptor.AroundLogMethodInterceptorImpl" />
<!-- 目标对象 -->
<bean id="target1" class="com.tom.springnote.common.aop.ManNoItfCallTask" />
<bean id="target2" class="com.tom.springnote.common.aop.ManNoItfCallTask" />
<!-- 注册切面 -->
<bean id="timeCostAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="pointcut" ref="pointcut"/>
<property name="advice" ref="timeCostMethodInterceptorImpl" />
</bean>
<bean id="aroundLogAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="pointcut" ref="pointcut"/>
<property name="advice" ref="aroundLogMethodInterceptorImpl" />
</bean>
</beans>
【5】基于AspectJ类库注解自动织入
1)@AspectJ代表一种定义Aspect切面的风格, 让我们能够以POJO形式定义Aspect(编码简单),没有其他接口定义限制;
2)通过AspectJ类库注解重构切面,注解如下:
- @Aspect标注切面类;
- @Pointcut标注pointcut表达式所在方法;
- @Around, @Before, @AfterThrowing, @AfterReturning, @After 标注横切逻辑方法;与实现BeforeAdvice接口类似效果;
3)使用AspectJ注解织入,需要使用AnnotationAwareAspectJAutoProxyCreator作为织入器;
【5.1】基于AspectJ注解的硬编码织入(AspectJProxyFactory作为织入器)
【ManNoItfCallTaskAspectjAnnotationMain】基于AspectJ注解织入横切逻辑测试main
public class ManNoItfCallTaskAspectjAnnotationMain {
public static void main(String[] args) {
AspectJProxyFactory weaver = new AspectJProxyFactory();
weaver.setProxyTargetClass(true);
weaver.setTarget(new ManNoItfCallTask());
weaver.addAspect(TimeCostAspectByAnnotation.class);
// 获取代理对象
Object proxy = weaver.getProxy();
((ManNoItfCallTask) proxy).call(BusiMessage.build("任务编号001", "您有待办任务需要处理"));
System.out.println(proxy.getClass());
}
}
【TimeCostAspectByAnnotation】通过Aspect注解标注切面,包含pointcut注解标注切点表达式,around注解标注环绕通知(横切逻辑)
@Aspect
public class TimeCostAspectByAnnotation {
@Pointcut("execution(public void *.call(..))")
public void pointcutName() {
// pointcut
}
@Around("pointcutName()")
public Object timeCost(ProceedingJoinPoint joinPoint) throws Throwable {
StopWatch stopWatch = new StopWatch();
try {
System.out.println("stopWatch.start()");
stopWatch.start();
return joinPoint.proceed();
} catch (Exception e) {
System.out.println("抛出异常");
e.printStackTrace();
} finally {
System.out.println("stopWatch.stop()");
stopWatch.stop();
System.out.printf("方法执行耗时%s\n", stopWatch.getTotalTime(TimeUnit.SECONDS));
}
return null;
}
}
【5.2】基于AspectJ注解与dtdXml配置织入(AnnotationAwareAspectJAutoProxyCreator作为织入器)
【AspectByAnnotationAndXmlDtdMain】
public class AspectByAnnotationAndXmlDtdMain {
public static void main(String[] args) {
ClassPathXmlApplicationContext container =
new ClassPathXmlApplicationContext("chapter10/beans10aspectannotationdtd.xml");
// 获取代理对象
Object proxy = container.getBean("target");
((ManNoItfCallTask) proxy).call(BusiMessage.build("任务编号001", "您有待办任务需要处理"));
System.out.println(proxy.getClass());
}
}
【beans10aspectannotationdtd.xml】 ( 虽然只注册了切面和目标对象,我们没有手工织入,但 AnnotationAwareAspectJAutoProxyCreator 实现了自动织入 )
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator">
<property name="proxyTargetClass" value="true" />
</bean>
<bean id="timeCostAspectByAnnotation" class="com.tom.springnote.chapter10.aspectjannotation.TimeCostAspectByAnnotation" />
<bean id="target" class="com.tom.springnote.common.aop.ManNoItfCallTask"/>
</beans>
【5.3】基于AspectJ注解与xsdXml配置织入(AnnotationAwareAspectJAutoProxyCreator作为织入器)
【beans10aspectannotationxsd.xml】aop:aspectj-autoproxy 元素定义:( 底层还是使用 AnnotationAwareAspectJAutoProxyCreator自动代理类实现自动织入 )
<?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:context="http://www.springframework.org/schema/context"
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/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 使用 <aop:aspectj-autoproxy> 元素,用于替换 AnnotationAwareAspectJAutoProxyCreator 自动代理织入器 -->
<aop:aspectj-autoproxy proxy-target-class="true" />
<bean id="timeCostAspectByAnnotation" class="com.tom.springnote.chapter10.aspectjannotation.TimeCostAspectByAnnotation" />
<bean id="target" class="com.tom.springnote.common.aop.ManNoItfCallTask"/>
</beans>
【5.4】多个切面如何顺序执行
1) 问题: 一个切点可能匹配多个 advice, 这些advice执行 顺序是什么?
2)解决方法:
- 如果 Advice定义在同一个Aspect, 根据其定义顺序决定其执行顺序;
- 如果Advice定义在不同Aspect,这时可以通过实现 Ordered接口; 值越小,优先级越高;越先执行;
【beans10AspectjMultiAspectAnnotationXsd.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:context="http://www.springframework.org/schema/context"
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/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:aspectj-autoproxy proxy-target-class="true" />
<bean class="com.tom.springnote.chapter10.multiaspect.FirstMethodCallLogAspect" />
<bean class="com.tom.springnote.chapter10.multiaspect.SecondMethodCallLogAspect" />
<bean id="target" class="com.tom.springnote.chapter10.target.MessageDAO"/>
</beans>
【FirstMethodCallLogAspect】第1个切面 (order为100,值越大 优先级越小,越后执行 )
@Aspect
public class FirstMethodCallLogAspect implements Ordered {
@Before("execution(* *.qryMsg(..))")
public void requestLog() {
System.out.println("FirstMethodCallLogAspect#requestLog(): 方法被调用");
}
@Override
public int getOrder() {
return 100;
}
}
【SecondMethodCallLogAspect】第2个切面 (order为0,值越小, 优先级越高,越先执行 )
@Aspect
public class SecondMethodCallLogAspect implements Ordered {
@Before("execution(* *.qryMsg(..))")
public void requestLog() {
System.out.println("SecondMethodCallLogAspect#requestLog(): 方法被调用");
}
@Override
public int getOrder() {
return 0;
}
}
【6】基于SchemaXml配置织入
1)schema 指的是 xsd,xml schema definition,xml模式(结构)定义;
2)schema配置:指的是使用 <aop:config> 元素配置切面,通知,pointcut, <aop:config>元素包含 Pointcut, Advisor 及 Aspect共3个子元素;
【BasedSchemaXmlConfAopMain】基于schema的aop
public class BasedSchemaXmlConfAopMain {
public static void main(String[] args) {
ClassPathXmlApplicationContext container =
new ClassPathXmlApplicationContext("chapter10/basedSchema/beans10BasedSchema.xml");
// 获取代理对象
Object proxy = container.getBean("target");
((MessageDAO) proxy).qryMsg("task001");
}
}
【beans10BasedSchema.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:context="http://www.springframework.org/schema/context"
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/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:config proxy-target-class="true">
<aop:pointcut id="pointcut" expression="execution(* *.qryMsg(..))"/>
<aop:advisor id="advisor" pointcut-ref="pointcut" advice-ref="methodLogAroundMethodInterceptorImpl" order="1" />
</aop:config>
<bean id="methodLogAroundMethodInterceptorImpl"
class="com.tom.springnote.chapter10.basedschema.advice.MethodLogAroundMethodInterceptorImpl" />
<bean id="target" class="com.tom.springnote.chapter10.target.MessageDAO"/>
</beans>
【MethodLogAroundMethodInterceptorImpl】环绕通知 (没有使用 AspectJ注解)
public class MethodLogAroundMethodInterceptorImpl implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("MethodLogAroundMethodInterceptorImpl#invoke(): 方法调用前");
try {
return invocation.proceed();
} finally {
System.out.println("MethodLogAroundMethodInterceptorImpl#invoke(): 方法调用后");
}
}
}
【6.1】不推荐使用基于SchemaXml配置织入
1)不推荐使用基于SchemaXml配置织入: 因为切面,通知,pointcut配置在xml文件,而其具体实现是POJO,代码比较分散,比较复杂;
【7】TargetSource目标对象源
1)TargetSource定义: TargetSource 是插入在调用方与目标对象之间的拦截逻辑抽象;
- 原先调用链: 调用方 -> 目标对象;
- 使用TargetSource后的调用链: 调用方 -> TargetSource -> 目标对象;
2)使用TargetSource,程序就可以控制每次方法调用作用到的具体对象实例; 简单理解: TargetSource是目标对象容器,可以包含一个或多个目标对象 ;