探秘Spring AOP

编程范式概览

  • 面向过程编程
  • 面向对象编程
  • 函数式编程
  • 事件驱动编程
  • 面向切面编程

AOP是什么

  • 是一种编程范式,不是编程语言
  • 解决特定问题,不能解决所有问题
  • 是OOP的补充,不是替代

AOP的初衷

  • DRY:Don't Repeat Yourself 解决代码重复性问题
  • SoC:Separation of Concerns 解决关注点问题
    • 水平分离:展示层 --> 服务层 --> 持久层
    • 垂直分离:模块划分(订单、库存等)
    • 切面分离:分离功能性需求与非功能性需求(把非功能性需求从功能性需求中分离出来,从而实现DRY)

使用AOP的好处

  • 集中处理某一关注点/横切逻辑
  • 可以很方便地添加/删除关注点
  • 侵入性少,增强代码可读性及可维护性

AOP应用场景

  • 权限控制
  • 缓存控制
  • 事务控制
  • 审计日志
  • 性能监控
  • 分布式追踪
  • 异常处理

支持AOP的编程语言

  • Java
  • .Net
  • C/C++
  • Ruby
  • Python
  • PHP

注解:

  • @Aspect - 申明成切面类
  • @Pointcut("@annotation(AdminOnly)") - 申明表达式,拦截有AdminOnly的注解;
  • @Before("adminOnly()") - 在adminOnly()方法执行之前,执行该注解方法。

Spring AOP使用方式

  • XML配置
  • 注解方式

主要注解

  • AspectJ 注解
    • @Aspect:用来标注说明这个Java类是一个切面配置类
    • @Pointcut:描述在哪些类的哪些方法进行植入你的代码
    • Advice:你想要在这些方法的执行什么时机进行植入

Pointcut expression:切面表达式

  • designators:execution() 、 ...
  • wildcards:* / .. / +
  • operators:&& / || / !

Wildcards(通配符)

  • * :匹配任意数量的字符
  • + :匹配指定类及其子类
  • .. :一般用于匹配任意数的子包或参数

Operators(运算符)

  • && :与操作符
  • || :或操作符
  • ! :非操作符

designators

  • 匹配方法:execution()
  • 匹配注解:
    • @target()
    • @args()
    • @within()
    • @annotation()
  • 匹配包/类型
    • within()
  • 匹配对象
    • this()
    • bean()
    • target()
  • 匹配参数
    • args()

匹配包/类型

// 匹配ProductService类里头的所有方法
@Pointcut("within(com.imooc.service.ProductService)")
public void matchType(){}

// 匹配com.imooc包及子包下所有类的方法
@Pointcut("within(com.imooc..*)")
public void matchPackage(){}

匹配对象

// 匹配AOP对象的目标对象为指定类型的方法,即DemoDao的aop代理对象的方法
@Pointcut("this(com.imooc.DemoDao)")
public void thisDemo(){}

// 匹配实现IDao接口的目标对象(而不是aop代理后的对象)的方法,这里即DemoDao的方法
@Pointcut("target(com.imooc.IDao)")
public void targetDemo(){}

// 匹配所有以Service结尾的bean里头的方法
@Pointcut("bean(*Service)")
public void beanDemo(){}

参数匹配

// 匹配任何以find开头而且只有一个Long参数的方法
@Pointcut("execution(* *..find*(Long))")
public void argsDemo1(){}

// 匹配任何只有一个Long参数的方法
@Pointcut("args(Long)")
public void argsDemo2(){}

// 匹配任何以find开头的而且第一个参数为Long型的方法
@Pointcut("execution(* *..find*(Long,..))")
public void argsDemo3(){}

// 匹配第一个参数为Long型的方法
@Pointcut("args(Long,..)")
public void argsDemo4(){}

匹配注解

// 匹配方法标注有AdminOnly的注解的方法
@Pointcut("@annotation(com.imooc.demo.security.AdminOnly)")
public void annoDemo(){}

// 匹配标注有Beta的类底下的方法,要求的annotation的RetentionPolicy级别为CLASS
@Pointcut("@within(com.google.common.annotations.Beta)")
public void annoWithinDemo(){}

// 匹配标注有Repository的类底下的方法,要求的annotation的RetentionPolicy级别为RUNTIME
@Pointcut("@target(org.springframework.stereotype.Repository)")
public void annoTargetDemo(){}

// 匹配传入的参数类标注有Repository注解的方法
@Pointcut("@args(org.springframework.stereotype.Repository)")
public void annoArgsDemo(){}

execution()表达式

格式

execution(
    modifier-pattern?   - 修饰符
    ret-type-pattern    - 返回值
    declaring-type-pattern? - 描述包名
    name-pattern(param-pattern) - 描述方法名(方法参数)
    throws-pattern? - 匹配方法抛出的异常 
)

标注?表示可省略的!

5种Advice注解

1、@Before,前置通知
2、@After(finally),后置通知,方法执行完之后
3、@AfterReturning,返回通知,成功执行之后
4、@AfterThrowing,异常通知,抛出异常之后
5、@Around,环绕通知

Advice中的参数及结果绑定

// 拦截获取方法的参数进行校验
@Before(value="annoTargetVsWithinDemo() && within(com.imooc..*) && args(userId)")
public void beforeWithArgs(JoinPoint joinPoint, Long userId){
    System.out.println("before, args:"+userId) ;
}

// 打印方法的返回值
@AfterReturning(value="annoTargetVsWithinDemo() && within(com.imooc..*)",returning="returnValue")
public void getReulst(Obgect returnValue){
    if(returnValue != null){
        System.out.println("after return, result:"+returnValue) ;
    }
}

原理概述:织入的时机

1、编译期(AspectJ)
2、类加载时(AspectJ 5+)
3、运行时(Spring AOP)

原理概述:运行时织入

  • 运行时织入是怎么实现的? 代理对象
  • 从静态代理到动态代理
    • 静态代理的缺点:每当代理的方法越多时,重复的逻辑也越多。
    • 动态代理的两类实现:基于接口代理与基于继承代理。
    • 两大实现的代表:JDK代理与Cglib代理。
  • 基于接口代理与基于继承代理
  • JDK实现要点

    • 类:java.lang.reflect.Proxy
    • 接口:InvocationHandler
    • 只能基于接口进行动态代理
  • JDK代理源码解析

    • Proxy.newProxyInstance:生成代理类的实现类
    • getProxyClass0:生成指定的代理类、ProxyClassFactory、ProxyGenerator
    • newInstance

Cglib实现

static class DemoCglibInterceptor implements MethodInterceptor{
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable{
        System.out.println("before...");
        Object result = methodProxy.invokeSuper(obj, args);
        System.out.println("after...");
        return result ;
    }
}

public static void main(String[] args){
    Subject subject = (Subject)getProxy(RealSubject.class, new DemoCglibInterceptor());
    subject.request();
}

JDK与Cglib代理对比

  • JDK只能针对有接口的类的接口方法进行动态代理
  • Cglib基于继承来实现代理,无法对static、final类进行代理
  • Cglib基于继承来实现代理,无法对private、static方法进行代理

Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP代理可以直接使用容器中的其它bean实例作为目标,这种关系可由IOC容器的依赖注入提供。

DefaultAopProxyFactory

@SuppressWarnings("serial")
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable{

    @Override
    public AopProxy creatAopProxy(AdvisedSupport config) throws AopConfigException{
        if(config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)){
            Class<?> targetClass = config.getTargetClass();
            if(targetClass == null){
                throw new AopConfigException("TargetSource cannot determine target class " + 
                        "Either an interface or a target is required for proxy creation.")
            }
            if(targetClass.isInterface() || Proxy.isProxyClass(targetClass)){
                return new JdkDynamicAopProxy(config);
            }
            return new ObjenesisCglibAopProxy(config);
        }else{
            return new JdkDynamicAopProxy(config);
        }
    }
}

Spring创建代理的规则为:
1、如果目标对象实现了接口,则默认才用JDK动态代理。
2、如果目标对象没有实现接口,则才用CGLIB动态代理。
3、如果目标对象实现了接口,并指定为CGLIB代理,则使用CGLIB动态代理。

强制使用Cglib代理

@SpringBootApplication
// 强制使用cglib代理
@EnableAspectJAutoProxy(proxyTargetClass=true)
public class AopDemoApplication{
    public static void main(String[] args){
        SpringApplication.run(AopDemoApplication.class, args);
    }
}

安全校验@PreAuthorize

  • MethodSecurityInterceptor
  • PreInvocationAuthorizationAdviceVoter
  • ExpressionBasedPreInvocationAdvice

缓存@Cacheable

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

推荐阅读更多精彩内容