spring源码分析之AOP

AOP核心概念

横切关注点:对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点
切面(aspect):类是对物体特征的抽象,切面就是对横切关注点的抽象
连接点(joinpoint):被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器
切入点(pointcut):对连接点进行拦截的定义
通知(advice):所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类
目标对象:代理的目标对象
织入(weave):将切面应用到目标对象并导致代理对象创建的过程
引入(introduction):在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段

Spring对AOP的支持

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

1、默认使用Java动态代理来创建AOP代理,这样就可以为任何接口实例创建代理了
2、当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB

JDK动态代理

首先直接调用就可以了,为什么还需要代理。采用代理模式可以有效的将具体的实现与调用方进行解耦,通过面向接口进行编码完全将具体的实现隐藏在内部。

静态代理

先看看什么是静态代理。由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
首先创建一个接口和一个实现类。JDK代理都是基于接口的

public interface ISub {
    public void hello(String name);
}

public class SubImpl implements ISub {
    @Override
    public void hello(String name) {
        System.out.println("My name is "+name);
    }
}

然后写个代理类,也是实现了ISub

public class SubProxy implements ISub{
    ISub sub = new SubImpl();
    @Override
    public void hello(String name) {
        System.out.println("====之前====");
        sub.hello(name);
        System.out.println("====之后====");
    }
}

最后写客户端。这个Client只于Proxy交互,不需要去了解ISub和SubImpl里面的东西了。实现了解耦。

public class Client{
    public static void main(String[] args) {
        SubProxy subProxy = new SubProxy();
        subProxy.hello("lijia");
    }
}

那么静态代理有什么缺点呢。

  • 代理对象的一个接口只服务一种类型的对象,如果要代理的方法很多,那么必须要为每一种方法都进行代理,静态代理在程序规模稍大就无法胜任了。
  • 如果接口增加一个方法,那么所有实现类和代理类都要增加这个方法。增加系统维护复杂度。

动态代理

动态代理的思维模式与之前的一般模式是一样的,也是面向接口进行编码,创建代理类将具体类隐藏解耦,不同之处在于代理类的创建时机不同,动态代理需要在运行时因需实时创建。
接口和实现类还是上面的。
主要是代理类。代理类实现了InvocationHandler。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class DynamicSubProxy implements InvocationHandler {
    private Object object;

    public DynamicSubProxy(Object o){
        this.object = o;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("====之前====");
        method.invoke(object,args);
        System.out.println("====之后====");
        return null;
    }
}

测试类

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class DynamicTest {
    public static void main(String[] args) {
        ISub sub = new SubImpl();
        InvocationHandler ih = new DynamicSubProxy(sub);
        ISub o = (ISub) Proxy.newProxyInstance(ISub.class.getClassLoader(),new Class[]{ISub.class},ih);
        o.hello("lijia");
    }
}

总结:

  1. 首先JDK的动态代理是基本接口实现的。
    因为通过使用接口指向实现类的实例的多态实现方式,可以有效的将具体实现类的细节向调用方完全隐藏(调用方调用的是代理类中的方法,而不是实现类中的方法)。面向接口编程,利用java的多态特性,实现程序代码的解耦
  2. 创建代理类的过程。
    静态代理和动态代理都需要创建代理类。但是创建的方式不一样。
    静态代理创建的代理类需要知道对哪个接口,哪个实现类来创建代理。而动态代理只需要实现一个固定的接口InvocationHandler
  3. 主要通过反射在运行期间创建。而静态代理之前就需要创建好。

CGLIB代理

JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLIB代理

还是用上面的实现类,没有接口了

public class SubImpl1{
    public void hello(String name) {
        System.out.println("My name is "+name);
    }
}

cglib代理类

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibProxy implements MethodInterceptor {
    private Enhancer enhancer = new Enhancer();
    public Object getProxy(Class clazz){
        //设置需要创建子类的类
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        //通过字节码技术动态创建子类实例
        return enhancer.create();
    }
    //实现MethodInterceptor接口方法
    @Override
    public Object intercept(Object obj, Method method, Object[] args,
                            MethodProxy proxy) throws Throwable {
        System.out.println("====之前====");
        //通过代理类调用父类中的方法
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("====之后====");
        return result;
    }
}

客户端

public class CglibTest {
    public static void main(String[] args) {
        CglibProxy proxy = new CglibProxy();
        SubImpl1 sub = (SubImpl1) proxy.getProxy(SubImpl1.class);
        sub.hello("lijia");
    }
}

总结:
CGLib原理是对指定的目标生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。
CGLib创建的动态代理对象性能比JDK创建的动态代理对象的性能高不少,但是CGLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CGLib合适,反之,使用JDK方式要更为合适一些。

AOP代码分析

首先还是通过一个例子来看看。
创建一个切面类

package aop.aspects;

import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class MyAspect1 {
    public MyAspect1()
    {
        System.out.println("MyAspect1 created ");
    }

    @Before("execution(* spring.services.*.*(..))")
    public void traceBusiness() {
        System.out.println( "in spring advice ");

    }
    @Before("execution(* spring.services.*.*(..))")
    public void traceBusiness(JoinPoint jp) {
        System.out.println("joinpoint [  "+jp.getKind() + " ,declaringTypeName "
                + jp.getSignature().getDeclaringTypeName()+"\r\n this "+jp.getThis().getClass().getName() + "\r\n,target:" + jp.getTarget() + " , method "
                + jp.getSignature().getName() + ",args:" + Arrays.toString(jp.getArgs()));

    }
    @After("execution(* spring.services.*.*(..))")
    public void afterBusiness() {
        System.out.println( "after aop");

    }
    @Around("execution(* spring.services.*.*(..))")
    public void traceBusiness(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("before enter method "+jp.getSignature().getName());
        jp.proceed(jp.getArgs());
        System.out.println("after enter method "+jp.getSignature().getName());

    }
}

创建接口:

package spring.services;

public interface MyDemoServiceIntf {

    public void doBusinessBBB(boolean suc);
}

创建MyDemoServiceIntf 实现类

package spring.services;

import org.springframework.stereotype.Component;

@Component("myTestService2")
public class MyTestService2 {
    public MyTestService2()
    {
    System.out.println("MyTestService2 craeated "+this);
    }
    public void doBusinessAAA(String[] args,boolean suc)
    {
         
    }
    
    public void doBusinessBBB(boolean suc)
    {
        System.out.println("=====进入了BBBBB");
        doBusinessCCC();
    }
    public void doBusinessCCC()
    {
        System.out.println("=====CCCCCC");
    }
}

配置类

import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan("spring.services,aop.aspects")
@EnableAspectJAutoProxy(proxyTargetClass=true,exposeProxy=true)
//@EnableAspectJAutoProxy(proxyTargetClass=true)
@Configurable
public class MySpringConfig {
    
public MySpringConfig()
{
    System.out.println("MySpringConfig create d ");
}
}

测试类

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import spring.services.MyTestService2;

public class Test4 {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MySpringConfig.class);
        MyTestService2 mybean = (MyTestService2) ctx.getBean("myTestService2");
        mybean.doBusinessBBB(true);
        ctx.close();
    }

}

运行结果:


看结果顺序,
首先是几个构造函数初始化。
然后是进入@Around的System.out.println("before enter method "+jp.getSignature().getName());
再走了@Before
走完了之后执行业务代码,
最后@After
然后走@Around的System.out.println("after enter method "+jp.getSignature().getName());

注解

  1. arg() 限制连接点匹配参数为指定类型的执行方法;
  2. @args() 限制连接点匹配参数由指定注解标注的执行方法;
  3. execution() 用于匹配是连接点的执行方法;
  4. this() 限制连接点匹配AOP代理的bean引用为指定类型的类
  5. target 限制连接点匹配目标对象为指定类型的类
  6. @target() 限制连接点匹配特定的执行对象,这些对象对应的类要具有指定类型的注解
  7. within() 限制连接点匹配指定的类型
  8. @within() 限制连接点匹配特定注解所标注的类型
  9. @annotation 限定匹配带有指定注解的连接点

总结

Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。

Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。

如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。

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

推荐阅读更多精彩内容