(FS计划)理清楚Spring的AOP到底怎么玩


前面简单介绍了Spring中AOP的使用,是基于XML配置。这次详细介绍一下Spring中AOP的使用和实现。

0x00 回顾

package com.zing.aspect_oriented_test;
/**
 * 方法切面
 */
public class SubjectService implements InterfaceSubjectService {
    @Override
    public void AspectMethod(String str) {
        System.out.println("yo yo yo!\t" + str);
    }
}
package com.zing.aspect_oriented_test;
/**
 * 对切面的处理,之前与之后
 */public class AspectTarget {
    public void beforeYouTalk() {
        System.out.println("************beforeYouTalk, I know everything,so do not lie!");
    }
    private void afterYouTalk() {
        System.out.println("************afterYouTalk, ha ha ha,good boy");
    }
}

我们知道了切面和切面处理方法,接下来告诉Spring,切面位置和处理方法
在XML配置文件中将两个bean配置到IoC容器中

<bean id="aspectService" class="com.zing.aspect_oriented_test.SubjectService"></bean>
<bean id="aspect" class="com.zing.aspect_oriented_test.AspectTarget"></bean>

再配置切面和切面方法

<aop:config>
    <aop:pointcut id="pointCut" expression="execution(* com.zing...*.*(..))"></aop:pointcut>
    <aop:aspect ref="aspect">
        <aop:before pointcut-ref="pointCut" method="beforeYouTalk"></aop:before>
        <aop:after pointcut="execution(* com.zing..*.*(..))"  method="afterYouTalk"></aop:after>
    </aop:aspect>
</aop:config>

上面的配置可能不太明白,因为用的是AspectJ语法,* com.zing..*.*(..)表示com.zing包下的所有子包的类与方法

例子撸完,写个Junit测试一下

package com.zing.aspect_oriented_test;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
 * Created by zing on 16/4/23.
 */
public class AspectJunitTest {
    @Test
    public void AopTest(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-config.xml");
        InterfaceSubjectService subjectService = applicationContext.getBean("aspectService",InterfaceSubjectService.class);
        subjectService.AspectMethod("Monster");
    }
}

想必看完云里雾里的,这里面用到了AOP的配置,完全没有概念,接下来详细解释一下,配置的含义

0x01 Aspect切面

Aspect,是织入的节点,前面两篇文章已经介绍了代理,而代理的节点,就是用Aspect来标记的,因为Spring封装了优秀的AspectJ解决方案,Aspect作为一个既定的接口,被Spring扩展了多个具体类型的通知BeforeAdvice,AfterAdvice,ThrowsAdvice

BeforeAdvice中分析,Spring中将前置接口设定为MethodBeforeAdvice,这个接口中只需要实现before方法

public interface MethodBeforeAdvice extends BeforeAdvice {
    void before(Method var1, Object[] var2, Object var3) throws Throwable;
}

这个方法将会在目标方法执行前被回调。

同样AfterAdvice是后置通知,具体的继承有AfterReturningAdvice

public interface AfterReturningAdvice extends AfterAdvice {
    void afterReturning(Object var1, Method var2, Object[] var3, Object var4) throws Throwable;
}

实现这个接口的afterReturning方法,在目标方法成功执行并返回值之后,AOP会回调afterReturning方法

最后ThrowsAdvice,其实是AfterAdvice子接口,在目标方法执行发生异常时,会被回调。
具体可以这么实现


class BoomException implements ThrowsAdvice{
    public void afterThrowing(IOException ioEx){
        System.out.println("IO异常:"+ioEx.getMessage());
    }

    public void afterThrowing(ClassCastException ccEx){
        System.out.println("转换异常:"+ccEx.getMessage());
    }
}

0x02 Pointcut切点

切点定义了一个代理接入位置,决定了通知作用的连接点。当然,也可以是一堆连接点,一般用一个正则表达式标识。

public interface Pointcut {
    Pointcut TRUE = TruePointcut.INSTANCE;

    ClassFilter getClassFilter();

    MethodMatcher getMethodMatcher();
}

其中getMethodMatcher()就是获取一个方法过滤器,这个方法过滤器将符合标准的方法,作为切面的连接点。
关于MethodMatcher,可以查看一下Spring源码。这里不做过多解释。

0x03 Advisor通知器

配置中并没有写Advisor,所以简单介绍一下,一个完整的模块,当要进行AOP编程时,需要将方法标记为切面,并定义了切面前置通知、后置通知、异常通知。定义完成,需要通过通知器,将切面和通知绑定起来,这个通知器就是Advisor。

Advisor将Advice和Pointcut结合起来,通过IoC容器来配置AOP来使用。

0x04 ProxyFactoryBean

ProxyFactoryBean是Spring利用Java的代理模式或者CGLIB来实现Aop的一种方式,如何在XML中配置ProxyFactoryBean?

  • 通知器Advisor使用Bean来配置。
  • 织入方法类使用Bean配置
  • 定义ProxyFactoryBean,为这个bean配置几个参数:
    • 目标:target
    • 代理接口:proxyInterface
    • 织入类:interceptName

如果不清楚可以看AOP和Spring中AOP的简单介绍中的第0x03小节的例子。这里就扣下代码,里面还有配置后置方法织入,异常方法通知织入,不一一介绍。

  <bean id="aspectService" class="com.zing.aspect_oriented_test.SubjectService"></bean>
  <bean id="aspect" class="com.zing.aspect_oriented_test.AspectTarget"></bean>

  <bean id="rocketProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" ref="launchingControl"></property>
        <property name="proxyInterfaces" value="com.zing.aoptest.IRocketLaunching"></property>
        <property name="interceptorNames" value="beforeLaunch"></property>
        <property name="proxyTargetClass" value="true"></property>
    </bean>

因为ProxyFactoryBean是依靠Java或CGLIB的Proxy方式来获取对象的,使用依靠代理的getObject()方法来作为入口。使用接下来看一下这个方法的实现方式

public Object getObject() throws BeansException {
//初始化通知器
        this.initializeAdvisorChain();
//区分单例模式和原始模式prototype
        if(this.isSingleton()) {
            return this.getSingletonInstance();
        } else {
            if(this.targetName == null) {
                this.logger.warn("Using non-singleton proxies with singleton targets is often undesirable. Enable prototype proxies by setting the \'targetName\' property.");
            }
            return this.newPrototypeInstance();
        }
    }

具体可以追一追newPrototypeInstance()getSingletonInstance()两个方法,得到实现方式的完整过程。留给感兴趣的小伙伴。因为再往里挖就挖到CGLIB和JDK对象生成里去了,感觉刨过头了。

0x05 Schema的AOP配置

前面啰嗦了一大堆,我觉得应该介绍一下具体的配置方式

 <!--aop定义开始-->
    <aop:config>
        <!--定义通知器-->
        <aop:advisor ref="aspectSupportBean"></aop:advisor>
        <!--定义切面 ref表示引用的bean-->
        <aop:aspect ref="aspectSupportBean">
            <!--定义切面增强位置-->
            <aop:pointcut id="pcut" expression="execution(* cn.javass..*.*(..))" ></aop:pointcut>
            <!--前置通知,下面的参数跟第一个类似-->
            <aop:before pointcut="切入点表达式" pointcut-ref="切入点Bean引用"  method="前置通知实现方法名" arg-names="前置通知实现方法参数列表参数名字"/>
            <!--后置返回通知-->
            <aop:after-returning></aop:after-returning>
            <!--异常通知-->
            <aop:after-throwing></aop:after-throwing>
            <!--最终通知-->
            <aop:after></aop:after>
            <!--环绕通知-->
            <aop:around></aop:around>
            <!--引入定义-->
            <aop:declare-parents  types-matching="AspectJ语法类型表达式" implement-interface=引入的接口"  default-impl="引入接口的默认实现"  delegate-ref="引入接口的默认实现Bean引用"/>
        </aop:aspect>
    </aop:config>

如果你看完前面的东西应该不难理解这些配置
但是有一个execution,为此查阅了一下文档,切入点指示符。执行表达式的格式如下:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)

除了返回类型模式(上面代码片断中的ret-type-pattern),名字模式和参数模式以外, 所有的部分都是可选的。返回类型模式决定了方法的返回类型必须依次匹配一个连接点。 你会使用的最频繁的返回类型模式是*,它代表了匹配任意的返回类型。 一个全限定的类型名将只会匹配返回给定类型的方法。名字模式匹配的是方法名。
你可以使用*通配符作为所有或者部分命名模式。 参数模式稍微有点复杂:()

匹配了一个不接受任何参数的方法, 而(..)

匹配了一个接受任意数量参数的方法(零或者更多)。 模式(*)

匹配了一个接受一个任何类型的参数的方法。 模式(*,String)

匹配了一个接受两个参数的方法,第一个可以是任意类型, 第二个则必须是String类型。

0x06 @AspectJ的AOP

是基于注解的AOP,默认Spring是不开启的,需要再XML里添加一行配置

<aop:aspectj-autoproxy/>

之后便可以用注解的方式使用AOP了,我列举一下

//配置切面
@Aspect() 
Public class Aspect{ 
…… 
}
//配置织入方法
@Pointcut(value="切入点表达式", argNames = "参数名列表") 
public void pointcutName(……) {}
//前置通知
@Before(value = "切入点表达式或命名切入点", argNames = "参数列表参数名")

//后置返回通知
@AfterReturning( 
value="切入点表达式或命名切入点", 
pointcut="切入点表达式或命名切入点", 
argNames="参数列表参数名", 
returning="返回值对应参数名") 
// value:指定切入点表达式或命名切入点;
// pointcut:同样是指定切入点表达式或命名切入点,如果指定了将覆盖value属性指定的,pointcut具有高优先级;
// argNames:与Schema方式配置中的同义;
// returning:与Schema方式配置中的同义。

//后置通知
@After ( 
value="切入点表达式或命名切入点", 
argNames="参数列表参数名") 
//value:指定切入点表达式或命名切入点;
// argNames:与Schema方式配置中的同义;


//异常通知
@AfterThrowing ( 
value="切入点表达式或命名切入点", 
pointcut="切入点表达式或命名切入点", 
argNames="参数列表参数名", 
throwing="异常对应参数名")

//环绕通知
@Around ( 
value="切入点表达式或命名切入点", 
argNames="参数列表参数名")

//引用
@DeclareParents( 
value=" AspectJ语法类型表达式", 
defaultImpl=引入接口的默认实现类) 
private Interface MyInterface;

只是简化了配置,用起来跟Schema类似

参考

http://www.importnew.com/17795.html
http://www.importnew.com/17813.html
《Spring 内幕技术》

转载请注明出处:理清楚Spring的AOP到底怎么玩


FS全栈计划目录:https://micorochio.github.io/fs-plan/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,580评论 18 139
  • 本博中关于spring的文章:Spring IOC和AOP原理,Spring事务原理探究,Spring配置文件属性...
    Maggie编程去阅读 4,095评论 0 34
  • title: Spring_AOP源码分析date: 2016-11-03 01:15:11categories:...
    raincoffee阅读 1,731评论 2 36
  • 对于常用标签div和span 就不多说了,在我们开始接触HTML5的时候最先了解的就应该是他们了。div和span...
    经典式微笑阅读 387评论 1 1
  • 盛唐的月 大秦的雨 千年的禅寺 佛经里的茶 几章回的英雄 还有夜里的银河倒泻 以及淋湿的惊慌失措的你
    艾特五百里阅读 158评论 0 0