Spring AOP(2)原理(代理模式和源码解析)

目录

一、代理模式

二、静态代理

三、动态代理

1、JDK动态代理

(1)JDK动态代理实现步骤

(2)定义JDK动态代理类

(3)代码简单讲解

2、CGLIB动态代理

(1)CGLIB 动态代理类实现步骤

(2)添加依赖

(3)自定义MethodInterceptor(方法拦截器)

(4)创建代理类,并使用

(5)代码简单讲解

四、Spring AOP 源码剖析(了解)

五、常见面试题

1、什么是 AOP?

2、Spring AOP的实现方式有哪些?

3、Spring AOP 的实现原理?

4、Spring 使用的是哪种代理方式?

5、JDK 和 CGLIB 的区别?

六、总结


        上篇文章学习了 Spring AOP 的应用,接下来我们来学习 Spring AOP 的原理,也就是 Spring 是如何实现 AOP 的。

        Spring AOP 是基于动态代理来实现 AOP 的,咱门学习内容主要分以下两部分:

1、代理模式

2、Spring AOP 源码 剖析


一、代理模式

        代理模式,也叫 委托模式。

定义为其他对象提供一种代理以控制这个对象的访问它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用

       某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介作用。

        使用代理前:

        使用代理后:

        

        生活中的代理:

艺人经纪人:广告商找艺人拍广告,需要经过经纪人,由经纪人来和艺人沟通。

房屋中介:房屋进行租赁时,卖方会把房屋授权给中介,由中介来代理看房,房屋咨询等服务。

经销商:厂商不直接对外销售产品,由经销商负责代理销售。

秘书/助理:合作伙伴找老板谈合作,需要先经过秘书/助理预约。

        代理模式的主要角色:

1、Subject业务接口类。可以是抽象类或者接口(不一定有)

2、RealSubject业务实现类。具体的业务执行,也就是被代理对象

3、Proxy代理类。RealSubject的代理

比如 房屋出租:

        Subject:就是提前定义了房东做的事情,交给中介代理,也是中介要做的事情。

        RealSubject:房东。

        Proxy:中介。

        UML类图如下:

        

        代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强根据代理的创建时期,代理模式分为静态代理和动态代理

        静态代理由程序员创建代理类或特定工具自动生成源代码再对其编译,在程序运行前代理类的 .class 文件就已经存在了

        动态代理在程序运行时,运用反射机制动态创建而成


二、静态代理

        静态代理:在程序运行前,代理类的 .class文件 就已经存在了。(在出租房子之前,中介已经做好了相关的工作,就等租户来租房子了)。

        我们通过代码来加深理解。以房租租赁为例:

1、定义接口(定义房东要做的事情,也是中介需要做的事情)

public interface HouseSubject {
    void rentHouse();
}

2、实现接口(房东出租房子)

public class RealHouseSubject implements HouseSubject{
    @Override
    public void rentHouse() {
        System.out.println("我是房东, 我出租房子");
    }
}

3、代理(中介,帮房东出租房子)

public class HouseProxy implements HouseSubject{
    private HouseSubject target;

    public HouseProxy(HouseSubject target) {
        this.target = target;
    }

    @Override
    public void rentHouse() {
        //代理前
        System.out.println("我是中介, 开始代理");
        //出租房子
        target.rentHouse();;
        //代理后
        System.out.println("我是中介, 结束代理");
    }
}

4、使用

public class Main {
    public static void main(String[] args) {
        HouseSubject subject = new RealHouseSubject();
        //创建代理类
        HouseProxy houseProxy = new HouseProxy(subject);
        //通过代理类访问⽬标⽅法
        houseProxy.rentHouse();
    }
}

        运行结果:

        上面这个代理实现方式就是静态代理(仿佛啥也没干)。从上述程序可以看出,虽然静态代理也完成了对目标对象的代理,但是由于代码都写死了,对目标对象的每个方法的增强都是手动完成的,非常不灵活。所以日常开发几乎看不到静态代理的场景。

        接下来新增需求:中介又新增了其他业务:代理房屋出售。我们就需要对上述代码进行修改。

1、接口定义修改:

public interface HouseSubject {
    void rentHouse();
    void saleHouse();
}

2、接口实现修改

public class RealHouseSubject implements HouseSubject{
    @Override
    public void rentHouse() {
        System.out.println("我是房东, 我出租房子");
    }

    @Override
    public void saleHouse() {
        System.out.println("我是房东, 我出售房子");
    }
}

3、代理类修改

public class HouseProxy implements HouseSubject{
    private HouseSubject target;

    public HouseProxy(HouseSubject target) {
        this.target = target;
    }

    @Override
    public void rentHouse() {
        //代理前
        System.out.println("我是中介, 开始代理");
        //出租房子
        target.rentHouse();;
        //代理后
        System.out.println("我是中介, 结束代理");
    }

    @Override
    public void saleHouse() {
        //代理前
        System.out.println("我是中介, 开始代理");
        //出租房子
        target.rentHouse();;
        //代理后
        System.out.println("我是中介, 结束代理");
    }
}

4、使用

public class Main {
    public static void main(String[] args) {
        HouseSubject subject = new RealHouseSubject();
        //创建代理类
        HouseProxy houseProxy = new HouseProxy(subject);
        //通过代理类访问⽬标⽅法
        houseProxy.rentHouse();
        System.out.println("=========");
        houseProxy.saleHouse();
    }
}

        运行结果:

        从上述代码可以看出,我们修改接口(Subject)和业务实现类(RealSubject),还需要修改代理类(Proxy)

        同样的,如果新增接口(Subject)和业务实现类(RealSubject),也需要对每一个业务实现类新增代理类(Proxy)

        既然代理的流程是一样的,有没有一种办法,让他们通过一个代理类来实现呢?这就需要用到动态代理技术了。


三、动态代理

        相比于静态代理来说,动态代理更加灵活。我们不需要针对每个目标对象都单独创建一个代理对象,而是把这个创建代理对象的工作推迟到程序运行时由JVM来实现也就是说动态代理在程序运行时,根据需要动态创建生成

        比如房屋中介,我不需要提前预测都有哪些业务,而是业务来了我再根据情况创建。

        先看代码再来理解。Java也对动态代理进行了实现,并给我们提供一些API,常见的实现方式有两种:

        1、JDK动态代理

        2、CGLIB动态代理

动态代理在我们日常开发中使用的相对较少,但是在框架中几乎是必用的一门技术。学会了动态代理之后,对于我们理解和学习各种框架的原理也非常有帮助。

1、JDK动态代理

(1)JDK动态代理实现步骤

1、定义一个接口及其实现类(静态代理中的 HouseSubject 和 RealHouseSubject)

2、自定义 InvocationHandler 并重写 invoke 方法,在 invoke 方法中我们会调用目标方法(被代理类的方法),并自定义一些处理逻辑

3、通过 Proxy.newProxyInstance(ClassLoader, Class<?>[ ]  Interfaces, InvocationHandler  h)方法创建代理对象

(2)定义JDK动态代理类

        创建 JDKInvocationHandler类 实现 InvocationHandler 接口:

public class JDKInvocationHandler implements InvocationHandler {
    //目标对象,即被代理的对象
    private Object target;

    public JDKInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //代理增强内容
        System.out.println("我是中介,开始代理");
        //通过反射调用被代理类的方法
        Object result = method.invoke(target, args);
        //代理增强内容
        System.out.println("我是中介,结束代理");
        return result;
    }
}

        创建一个代理对象并使用:

public class Main {
    public static void main(String[] args) {
        /**
         * JDK动态代理
         */

        //创建⼀个代理类:通过被代理类、被代理实现的接⼝、⽅法调⽤处理器来创建
        RealHouseSubject target = new RealHouseSubject();//目标对象
        /**
         * newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
         * loader:加载我们的被代理类的ClassLoad
         * interfaces:要实现的接口
         * h:代理要做的事情,需要实现 InvocationHandler 这个接口
         */
        HouseSubject proxy = (HouseSubject) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                new Class[]{HouseSubject.class},
                new JDKInvocationHandler(target)
        );
        proxy.rentHouse();
        System.out.println("==============");
        proxy.saleHouse();
    }
}

        运行程序,结果如下:

        假设代理的是类,而不是对象,代码如下:

public class Main {
    public static void main(String[] args) {
        RealHouseSubject target = new RealHouseSubject();
        RealHouseSubject proxy = (RealHouseSubject) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                new Class[]{RealHouseSubject.class},
                new JDKInvocationHandler(target)
        );
        proxy.rentHouse();
        System.out.println("==============");
        proxy.saleHouse();
    }
}

        运行程序,结果如下:(报错了)

        报错原因RealHouseSubject is not an interface(RealHouseSubject 类不是接口)说明JDK 动态代理只能代理接口,不能代理类,不然会报错。

(3)代码简单讲解

        主要是学习API的使用,我们按照 Java API 的规范来使用即可。

1、InvocationHandler:

        InvocationHandler 接口是 Java 动态代理的关键接口之一,它定义了一个单一方法 invoke(),用于处理被代理对象的方法调用。

    public interface InvocationHandler {
        /**
         * 参数说明
         * proxy:被代理对象
         * method:被代理对象需要实现的⽅法,即其中需要重写的⽅法
         * args:method所对应⽅法的参数
         */
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable;
    }

        通过实现 InvocationHandler 接口,可以对被代理对象的方法进行功能增强。

2、Proxy:

        Proxy 类中使用频率最高的方法:newProxyInstance(),这个方法主要用来生成一个代理对象。

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
            throws IllegalArgumentException
    {
        //...代码省略
    }

        这个方法一共有 3 个参数:

loader类加载器,用于加载被代理对象

interface被代理类实现的一些接口(这个参数的定义,也决定了JDK动态代理只能代理实现了接口的一些类)

h代理要做的事情,实现 InvocationHandler 接口的对象

2、CGLIB动态代理

        JDK动态代理有一个最致命的问题是只能代理实现了接口的类

        有些场景下,我们的业务码是直接实现的,并没有接口定义。为了解决这个问题,我们可以用 CGLIB 动态代理机制来解决。

        CGLIB(Code Generation Library)是一个基于 ASM 的字节码生产库,它允许我们在运行时对字节码进行修改和动态生成。

        CGLIB 通过继承方式实现代理,很多知名的开源框架都使用到了 CGLIB。例如 Spring 中的 AOP 模块中:如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。(其中 Spring 是基于动态代理实现的,动态代理是基于反射实现的

(1)CGLIB 动态代理类实现步骤

1、定义一个类(被代理类)

2、自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于增强目标方法,和 JDK 动态代理中的 invoke 方法类似

3、通过 Enhancer 类的 create() 创建代理类

        接下来看实现:

(2)添加依赖

        和 JDK 动态代理不同,CGLIB(Code Generation Library)实际是属于一个开源项目,如果你要使用它的话,需要手动添加相关依赖

        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
        </dependency>

(3)自定义MethodInterceptor(方法拦截器)

        实现 MethodInterceptor 接口:

import org.springframework.cglib.proxy.MethodInterceptor;
import java.lang.reflect.Method;

public class CGLibInterceptor implements MethodInterceptor {
    private Object target;

    public CGLibInterceptor(Object target) {
        this.target = target;
    }

    /**
     * 调用代理对象的方法
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, org.springframework.cglib.proxy.MethodProxy proxy) throws Throwable {
        //代理增强内容
        System.out.println("我是中介,开始代理");
        Object result = method.invoke(target, args);
        //代理增强内容
        System.out.println("我是中介,结束代理");
        return result;
    }
}

(4)创建代理类,并使用

        代理接口:

public class Main {
    public static void main(String[] args) {
        //目标对象
        HouseSubject target = new RealHouseSubject();
        HouseSubject proxy  = (HouseSubject) Enhancer.create(target.getClass(), new CGLibInterceptor(target));
        proxy.rentHouse();
        System.out.println("=============");
        proxy.saleHouse();
    }
}

        运行程序,执行结果如下:

        代理类:

public class Main {
    public static void main(String[] args) {
        //目标对象
        HouseSubject target = new RealHouseSubject();
        RealHouseSubject proxy  = (RealHouseSubject) Enhancer.create(target.getClass(), new CGLibInterceptor(target));
        proxy.rentHouse();
        System.out.println("=============");
        proxy.saleHouse();
    }
}

        运行程序,执行结果如下:

(5)代码简单讲解

1、MethodInterceptor

        MethodInterceptor 和 JDK动态代理中的 InvocationHandler 类似,它只定义了一个方法 intercept(),用于增强目标方法。

    public interface MethodInterceptor extends Callback {
        /**
         * 参数说明:
         * o: 被代理的对象
         * method: ⽬标⽅法(被拦截的⽅法, 也就是需要增强的⽅法)
         * objects: ⽅法⼊参
         * methodProxy: ⽤于调⽤原始⽅法
         */
        Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable;
    }

2、Enhancer.create()

    public static Object create(Class type, Callback callback) {
        //...代码省略
    }

        type被代理类的类型(类或接口)

        callback自定义方法拦截器 MethodInterceptor


四、Spring AOP 源码剖析(了解)

        Spring AOP 主要基于两种方式实现的:JDK 及 CGLIB 的方式。

        Spring 源码过于复杂,我们只摘出一些主要内容,以了解为主

        Spring 对于 AOP 的实现,基本都是靠 AnnotationAwareAspectJAutoProxyCreator 去完成 生成代理对象的逻辑在父类 AbstractAutoProxyCreator 中。

protected Object createProxy(Class<?> beanClass,@Nullable String beanName,
@Nullable Object[]specificInterceptors,TargetSource targetSource){
        if(this.beanFactory instanceof ConfigurableListableBeanFactory){
        AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory)
        this.beanFactory,beanName,beanClass);
        }
        //创建代理⼯⼚
        ProxyFactory proxyFactory=new ProxyFactory();
        proxyFactory.copyFrom(this);
        /**
         * 检查proxyTargetClass属性值,spring默认为false
         * proxyTargetClass 检查接⼝是否对类代理, ⽽不是对接⼝代理
         * 如果代理对象为类, 设置为true, 使⽤cglib代理
         */
        if(!proxyFactory.isProxyTargetClass()){
            //是否有设置cglib代理
            if(shouldProxyTargetClass(beanClass,beanName)){
            //设置proxyTargetClass为true,使⽤cglib代理
            proxyFactory.setProxyTargetClass(true);
        }else{
            /**
             * 如果beanClass实现了接⼝,且接⼝⾄少有⼀个⾃定义⽅法,则使⽤JDK代理
             * 否则CGLIB代理(设置ProxyTargetClass为true )
             * 即使我们配置了proxyTargetClass=false, 经过这⾥的⼀些判断还是可能会将其
             设为true
             */
            evaluateProxyInterfaces(beanClass,proxyFactory);
            }
            }
            Advisor[]advisors=buildAdvisors(beanName,specificInterceptors);
            proxyFactory.addAdvisors(advisors);
            proxyFactory.setTargetSource(targetSource);
            customizeProxyFactory(proxyFactory);
    
            proxyFactory.setFrozen(this.freezeProxy);
            if(advisorsPreFiltered()){
            proxyFactory.setPreFiltered(true);
        }

        // Use original ClassLoader if bean class not locally loaded in overriding class loader
        ClassLoader classLoader =getProxyClassLoader();
        if(classLoader instanceof SmartClassLoader&&classLoader != beanClass.getClassLoader()){
            classLoader=((SmartClassLoader)classLoader).getOriginalClassLoader();
        }
//从代理⼯⼚中获取代理
        return proxyFactory.getProxy(classLoader);
        }

        代理工厂有一个重要的属性:proxyTargetClass,默认值为false。也可以通过程序设置

proxyTargetClass⽬标对象代理⽅式
false实现了接口jdk代理
false未实现接口(只有实现类)cglib代理
true实现了接口cglib代理
true未实现接口(只有实现类)cglib代理

        可以通过 @EnableAspectJAutoProxy(proxyTargetClass = true) 来设置。

        注意:

Spring 默认 proxyTargetClass:false,会分为两种情况:

        实现了接口使用 JDK 代理

        普通类:       使用 CGLIB 代理

Spring Boot 2.X 开始,默认使用 proxyTargetClass:true

        默认使用 CGLIB 代理

        SpringBoot设置 @EnableAspectJAutoProxy 无效,因为 Spring Boot 默认使用 AopAutoConfiguration 进行装配。

        可以通过配置项 spring.aop.proxy-target-class=false 来进行修改,设置为 jdk 代理。

        使用 context.getBean() 需要添加注解,使 HouseProxy,RealHouseSubject 被 Spring 管理。测试 AOP 代理,需要把这些类交给 AOP 管理(自定义注解或使用 @Aspect)

        我们现在从源码中点进去看看代理工厂的代码:

public class ProxyFactory extends ProxyCreatorSupport {
    //...代码省略
    //获取代理
    public Object getProxy(@Nullable ClassLoader classLoader) {
    //分两步 先createAopProxy,后getProxy
        return createAopProxy().getProxy(classLoader);
    }

    protected final synchronized AopProxy createAopProxy() {
        if (!this.active) {
            activate();
        }
        return getAopProxyFactory().createAopProxy(this);
    }
    //...代码省略
}

        createAopProxy 的实现在 DefaultAopProxyFactory 中

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
    //...代码省略
    @Override
    public AopProxy createAopProxy(AdvisedSupport config) throws
            AopConfigException {
        /**
         * 根据proxyTargetClass判断
         * 如果⽬标类是接⼝, 使⽤JDK动态代理
         * 否则使⽤cglib动态代理
         */
        if (!NativeDetector.inNativeImage() &&
                (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) ||
                    ClassUtils.isLambdaClass(targetClass)) {
                return new JdkDynamicAopProxy(config);
            }
            return new ObjenesisCglibAopProxy(config);
        } else {
            return new JdkDynamicAopProxy(config);
        }
    }
    //...代码省略
}

        接下来就是创建代理了

JDK动态代理:

final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {
    //...代码省略
    @Override
    public Object getProxy(@Nullable ClassLoader classLoader) {
        if (logger.isTraceEnabled()) {
            logger.trace("Creating JDK dynamic proxy: " +
                    this.advised.getTargetSource());
        }
        return Proxy.newProxyInstance(determineClassLoader(classLoader),
                this.proxiedInterfaces, this);
    }
    //...代码省略
}

CGLIB动态代理:

class CglibAopProxy implements AopProxy, Serializable {
    //...代码省略
    @Override
    public Object getProxy(@Nullable ClassLoader classLoader) {
    //...代码省略
    // Configure CGLIB Enhancer...
        Enhancer enhancer = createEnhancer();
    // Generate the proxy class and create a proxy instance.
        return createProxyClassAndInstance(enhancer, callbacks);
    }
    //...代码省略
}

五、常见面试题

1、什么是 AOP?

        AOP是 面向切面编程,也是一种思想,切面指的是某一类特定问题,所以 AOP 也可以理解为 面向切面编程。

2、Spring AOP的实现方式有哪些?

(1)基于注解(@Aspect 或 自定义注解)

(2)基于 xml

(3)基于代理

3、Spring AOP 的实现原理?

        基于动态代理实现的,其中的动态代理有两种形式:(1)JDK   (2)CGLIB

4、Spring 使用的是哪种代理方式?

        Spring 的 proxyTargetClass 默认为:false,其中:实现了接口,使用 JDK 代理;普通类:使用CGLIB代理。

        Spring Boot 从 2.X 之后,proxyTargetClass 默认为:true,默认使用 CGLIB 代理

         现在我们测试一下:当这个值为 true 时,则使用的是动态代理

        

@SpringBootApplication
public class SpringAopApplication {
    public static void main(String[] args) {
       ApplicationContext context = SpringApplication.run(SpringAopApplication.class, args);
        //代理类
        TestController bean = context.getBean(TestController.class);
        System.out.println(bean);
    }
}

        要打断电才能观察到结果,如图:

        我们获取Spring管理的对象,打断点,观察对象名称,如图:

        可以看到,是 CGLIB代理。

当值设为 false 时;就要看代理对象是不是接口了,是接口用的就是JDK代理,代理对象是类就是CGLIB代理。

        因为要代理接口,所以现在重新创建一个接口,内容如下:
 

public interface IFace {
    void test();
}

        再创建一个成 Controller 类,实现上面这个类

@RequestMapping("/test2")
@RestController
public class TestController2 implements IFace{
    @MyAspect
    @RequestMapping("t1")
    @Override
    public void test() {
        System.out.println("测试测试");
    }
}

        main方法如下:

@SpringBootApplication
public class SpringAopApplication {
    public static void main(String[] args) {
       ApplicationContext context = SpringApplication.run(SpringAopApplication.class, args);

       //代理类
        TestController bean = context.getBean(TestController.class);
        System.out.println(bean);

        //代理接口
        IFace iFace = (IFace) context.getBean("testController2");
        System.out.println(iFace);
    }
}

        现在看看Spring对象,任然要使用断点才能看到,如图:

        看看bean对象,如图:

5、JDK 和 CGLIB 的区别?

        使用JDK 动态代理只能代理接口。

        使用 CGLIB 动态代理 既可以代理接口,也可以代理类。


六、总结

        1、AOP 是一种思想是对某一类事情的集中处理。Spring 框架实现了AOP,称之为 Spring AOP。

        2、Spring AOP 场景的实现方式有两种:(1)基于注解@Aspect来实现(2)基于自定义注解来实现还有一些更原始的方式,比如基于代理、基于 xml 配置的方式,但目标比较少见

        3、Spring AOP 是基于动态代理实现的,有两种方式:(1)基于 JDK 动态代理实现(2)基于 CGLIB 动态代理实现。运行时使用哪种方式与项目配置的代理对象有关

  • 47
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 24
    评论
评论 24
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

tao滔不绝

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值